added whole new module for JSF3, without copying old sources because of removed classes
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5582ffa
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/target/

+/test12/target/

+/test20/target/

+/test22/target/

+/test23/target/

+/test30/target/
\ No newline at end of file
diff --git a/assembly/pom.xml b/assembly/pom.xml
index 5f9a5c6..1f49635 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -69,6 +69,12 @@
                   <version>${project.version}</version>
                   <classifier>javadoc</classifier>
                 </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.myfaces.test</groupId>
+                  <artifactId>myfaces-test30</artifactId>
+                  <version>${project.version}</version>
+                  <classifier>javadoc</classifier>
+                </artifactItem>
               </artifactItems>
               <outputDirectory>${project.build.directory}/javadoc</outputDirectory>
             </configuration>
@@ -111,7 +117,7 @@
                   <version>${project.version}</version>
                   <type>zip</type>
                   <classifier>source-release</classifier>
-                </artifactItem>                
+                </artifactItem>
                </artifactItems>
                <outputDirectory>${project.build.directory}/src</outputDirectory>
              </configuration>
@@ -137,7 +143,7 @@
                 <phase>package</phase>
                 <goals>
                    <goal>attached</goal>
-                </goals>                
+                </goals>
             </execution>
             <execution>
                 <id>make_assembly_bin</id>
@@ -219,7 +225,7 @@
       <artifactId>myfaces-test22</artifactId>
       <version>${project.version}</version>
     </dependency>
-	
+
     <dependency>
       <groupId>org.apache.myfaces.test</groupId>
       <artifactId>myfaces-test23</artifactId>
diff --git a/pom.xml b/pom.xml
index 71d3e0d..2abd17d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
       <module>test20</module>
       <module>test22</module>
 	  <module>test23</module>
+	  <module>test30</module>
     </modules>
     <!--
     <repositories>
diff --git a/src/site/site.xml b/src/site/site.xml
index b643cfa..ab352b1 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -22,15 +22,15 @@
     <src>img/banners/MyFaces_logo.jpg</src>

     <href>http://myfaces.apache.org/index.html</href>

   </bannerLeft>

-    

+

   <bannerRight>

     <name>Apache Banner</name>

     <src>img/banners/apache_banner.png</src>

     <href>http://www.apache.org</href>

   </bannerRight>

-    

+

   <publishDate format="dd MMM yyyy" />

-    

+

   <skin>

     <groupId>org.apache.myfaces.maven</groupId>

     <artifactId>myfaces-site-skin</artifactId>

@@ -44,11 +44,13 @@
       <item name="1.2 Javadocs"  href="./myfaces-test12/apidocs/index.html"/>

       <item name="2.0 Javadocs"  href="./myfaces-test20/apidocs/index.html"/>

       <item name="2.2 Javadocs"  href="./myfaces-test22/apidocs/index.html"/>

+      <item name="2.3 Javadocs"  href="./myfaces-test23/apidocs/index.html"/>

+      <item name="3.0 Javadocs"  href="./myfaces-test30/apidocs/index.html"/>

       <item name="Download Myfaces Test" href="./download.html"/>

       <item name="Apache" href="http://www.apache.org"/>

       <item name="MyFaces" href="http://myfaces.apache.org/index.html"/>

     </links>

-  

+

     <menu name="Apache MyFaces" inherit="top">

       <item name="Overview"       href="http://myfaces.apache.org/index.html"/>

       <item name="Download"       href="http://myfaces.apache.org/download.html"/>

@@ -68,7 +70,7 @@
           <item name="dummy"        href="dummy"/>

         </item>

     </menu>

-    

+

     <menu name="UI-Component Sets" inherit="top">

         <item name="Trinidad"       href="../trinidad/index.html"/>

         <item name="Tobago"         href="../tobago/index.html"/>

@@ -76,7 +78,7 @@
           <item name="dummy"        href="dummy"/>

         </item>

     </menu>

-    

+

     <menu name="Add-ons and Extensions" inherit="top">

         <item name="CODI"           href="../extensions/cdi/index.html"/>

         <item name="Orchestra"      href="../orchestra/index.html"/>

diff --git a/test30/pom.xml b/test30/pom.xml
new file mode 100644
index 0000000..42012f7
--- /dev/null
+++ b/test30/pom.xml
@@ -0,0 +1,209 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $Id: pom.xml 1326869 2012-04-17 01:30:30Z lu4242 $
+ */
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.myfaces.test</groupId>
+        <artifactId>myfaces-test-project</artifactId>
+        <version>1.0.9-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>myfaces-test30</artifactId>
+    <packaging>jar</packaging>
+    <name>Myfaces Test Framework for JSF 3.0</name>
+
+    <dependencies>
+
+        <!-- Required only for using the org.apache.shale.test.config package -->
+        <dependency>
+            <groupId>commons-digester</groupId>
+            <artifactId>commons-digester</artifactId>
+            <version>1.8</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sourceforge.htmlunit</groupId>
+            <artifactId>htmlunit</artifactId>
+            <version>1.14</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>jmock</groupId>
+            <artifactId>jmock</artifactId>
+            <version>1.0.1</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>jmock</groupId>
+            <artifactId>jmock-cglib</artifactId>
+            <version>1.0.1</version>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- For the "org.apache.shale.test.cargo" package, we need to have  -->
+        <!-- JUnit as a compile time dependency, not just scope="test".      -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.5</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.cargo</groupId>
+            <artifactId>cargo-core-uberjar</artifactId>
+            <version>0.8</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.cargo</groupId>
+            <artifactId>cargo-ant</artifactId>
+            <version>0.8</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.portlet</groupId>
+            <artifactId>portlet-api</artifactId>
+            <version>1.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.myfaces.core</groupId>
+            <artifactId>myfaces-api</artifactId>
+            <version>${jsf-myfaces.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.myfaces.core</groupId>
+            <artifactId>myfaces-impl</artifactId>
+            <version>${jsf-myfaces.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <!--
+        <dependency>
+            <groupId>javax.faces</groupId>
+            <artifactId>javax.faces-api</artifactId>
+            <version>2.2</version>
+            <scope>provided</scope>
+        </dependency>-->
+
+        <!-- Servlet 3.0 -->
+        <dependency>
+             <groupId>org.apache.geronimo.specs</groupId>
+             <artifactId>geronimo-servlet_3.0_spec</artifactId>
+             <version>1.0</version>
+             <scope>provided</scope>
+        </dependency>
+        <!-- JSP 2.1 -->
+        <dependency>
+             <groupId>org.apache.geronimo.specs</groupId>
+             <artifactId>geronimo-jsp_2.1_spec</artifactId>
+             <version>1.0.1</version>
+             <scope>provided</scope>
+        </dependency>
+        <!-- EL 1.0 -->
+        <dependency>
+             <groupId>org.apache.geronimo.specs</groupId>
+             <artifactId>geronimo-el_1.0_spec</artifactId>
+             <version>1.0.2</version>
+             <scope>provided</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-source-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-source</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>generate-assembly</id>
+            <activation>
+                <property>
+                    <name>performRelease</name>
+                    <value>true</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+
+                    <plugin>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>attach-javadoc</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    <reporting>
+      <plugins>
+        <plugin>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <reportSets>
+            <reportSet>
+              <id>non-aggregate</id>
+              <configuration>
+              </configuration>
+              <reports>
+                <report>javadoc</report>
+              </reports>
+            </reportSet>
+          </reportSets>
+        </plugin>
+      </plugins>
+    </reporting>
+    <properties>
+        <jsf-myfaces.version>3.0.0-SNAPSHOT</jsf-myfaces.version>
+    </properties>
+</project>
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/AbstractJsfTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/AbstractJsfTestCase.java
new file mode 100644
index 0000000..4f76c28
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/AbstractJsfTestCase.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.myfaces.test.base;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.component.UIViewRoot;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.RenderKitFactory;
+
+import junit.framework.TestCase;
+
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.mock.MockApplication;
+import org.apache.myfaces.test.mock.MockExternalContext;
+import org.apache.myfaces.test.mock.MockFacesContext;
+import org.apache.myfaces.test.mock.MockFacesContextFactory;
+import org.apache.myfaces.test.mock.MockHttpServletRequest;
+import org.apache.myfaces.test.mock.MockHttpServletResponse;
+import org.apache.myfaces.test.mock.MockHttpSession;
+import org.apache.myfaces.test.mock.MockRenderKit;
+import org.apache.myfaces.test.mock.MockServletConfig;
+import org.apache.myfaces.test.mock.MockServletContext;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;
+
+/**
+ * <p>Abstract JUnit test case base class, which sets up the JavaServer Faces
+ * mock object environment for a particular simulated request.  The following
+ * protected variables are initialized in the <code>setUp()</code> method, and
+ * cleaned up in the <code>tearDown()</code> method:</p>
+ * <ul>
+ * <li><code>application</code> (<code>MockApplication</code>)</li>
+ * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+ * <li><code>externalContext</code> (<code>MockExternalContext</code>)</li>
+ * <li><code>facesContext</code> (<code>MockFacesContext</code>)</li>
+ * <li><code>lifecycle</code> (<code>MockLifecycle</code>)</li>
+ * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+ * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+ * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+ * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+ * </ul>
+ *
+ * <p>In addition, appropriate factory classes will have been registered with
+ * <code>javax.faces.FactoryFinder</code> for <code>Application</code> and
+ * <code>RenderKit</code> instances.  The created <code>FacesContext</code>
+ * instance will also have been registered in the proper thread local
+ * variable, to simulate what a servlet container would do.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case.</p>
+ * 
+ * @since 1.0.0
+ */
+
+public abstract class AbstractJsfTestCase extends TestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of this test case
+     */
+    public AbstractJsfTestCase(String name)
+    {
+        super(name);
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    /**
+     * <p>Set up instance variables required by this test case.</p>
+     */
+    protected void setUp() throws Exception
+    {
+        // Set up a new thread context class loader
+        setUpClassloader();
+
+        // Set up Servlet API Objects
+        setUpServletObjects();
+
+        // Set up JSF API Objects
+        FactoryFinder.releaseFactories();
+
+        setFactories();
+
+        setUpJSFObjects();
+    }
+    
+    /**
+     * Set up the thread context classloader. JSF uses the this classloader
+     * in order to find related factory classes and other resources, but in
+     * some selected cases, the default classloader cannot be properly set.
+     * 
+     * @throws Exception 
+     */
+    protected void setUpClassloader() throws Exception
+    {
+        threadContextClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        Thread.currentThread()
+                .setContextClassLoader(
+                        new URLClassLoader(new URL[0], this.getClass()
+                                .getClassLoader()));
+        classLoaderSet = true;
+    }
+
+    /**
+     * <p>Setup JSF object used for the test. By default it calls to the following
+     * methods in this order:</p>
+     * 
+     * <ul>
+     * <li><code>setUpExternalContext();</code></li>
+     * <li><code>setUpLifecycle();</code></li>
+     * <li><code>setUpFacesContext();</code></li>
+     * <li><code>setUpView();</code></li>
+     * <li><code>setUpApplication();</code></li>
+     * <li><code>setUpRenderKit();</code></li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpJSFObjects() throws Exception
+    {
+        setUpExternalContext();
+        setUpLifecycle();
+        setUpFacesContext();
+        setUpView();
+        setUpApplication();
+        setUpRenderKit();
+    }
+
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+     * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpServletObjects() throws Exception
+    {
+        servletContext = new MockServletContext();
+        config = new MockServletConfig(servletContext);
+        session = new MockHttpSession();
+        session.setServletContext(servletContext);
+        request = new MockHttpServletRequest(session);
+        request.setServletContext(servletContext);
+        response = new MockHttpServletResponse();
+    }
+
+    /**
+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>
+     * 
+     * @throws Exception
+     */
+    protected void setFactories() throws Exception
+    {
+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
+                "org.apache.myfaces.test.mock.MockApplicationFactory");
+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockFacesContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,
+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");
+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,
+                "org.apache.myfaces.test.mock.MockRenderKitFactory");
+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,
+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");
+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.CLIENT_WINDOW_FACTORY,
+                "org.apache.myfaces.test.mock.MockClientWindowFactory");
+    }
+
+    /**
+     * Setup the <code>externalContext</code> variable, using the 
+     * servlet variables already initialized.
+     * 
+     * @throws Exception
+     */
+    protected void setUpExternalContext() throws Exception
+    {
+        externalContext = new MockExternalContext(servletContext, request,
+                response);
+    }
+
+    /**
+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>
+     * variables.
+     * 
+     * @throws Exception
+     */
+    protected void setUpLifecycle() throws Exception
+    {
+        lifecycleFactory = (MockLifecycleFactory) FactoryFinder
+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        lifecycle = (MockLifecycle) lifecycleFactory
+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+    }
+
+    /**
+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>
+     * variable. Before end, by default it override <code>externalContext</code>
+     * variable from the value retrieved from facesContext.getExternalContext(),
+     * because sometimes it is possible facesContext overrides externalContext
+     * internally.
+     * 
+     * @throws Exception
+     */
+    protected void setUpFacesContext() throws Exception
+    {
+        facesContextFactory = (MockFacesContextFactory) FactoryFinder
+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+        facesContext = (MockFacesContext) facesContextFactory.getFacesContext(
+                servletContext, request, response, lifecycle);
+        if (facesContext.getExternalContext() != null)
+        {
+            externalContext = (MockExternalContext) facesContext
+                    .getExternalContext();
+        }
+    }
+
+    /**
+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"
+     * and assign it to the current facesContext.
+     * 
+     * @throws Exception
+     */
+    protected void setUpView() throws Exception
+    {
+        UIViewRoot root = new UIViewRoot();
+        root.setViewId("/viewId");
+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        facesContext.setViewRoot(root);
+    }
+
+    /**
+     * Setup the <code>application</code> variable and before
+     * the end by default it is assigned to the <code>facesContext</code>
+     * variable, calling <code>facesContext.setApplication(application)</code>
+     * 
+     * @throws Exception
+     */
+    protected void setUpApplication() throws Exception
+    {
+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder
+                .getFactory(FactoryFinder.APPLICATION_FACTORY);
+        application = (MockApplication) applicationFactory.getApplication();
+        facesContext.setApplication(application);
+    }
+
+    /**
+     * Setup the <code>renderKit</code> variable. This is a good place to use
+     * <code>ConfigParser</code> to register converters, validators, components
+     * or renderkits.
+     * 
+     * @throws Exception
+     */
+    protected void setUpRenderKit() throws Exception
+    {
+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+        renderKit = new MockRenderKit();
+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+                renderKit);
+    }
+
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    protected void tearDown() throws Exception
+    {
+
+        application = null;
+        config = null;
+        externalContext = null;
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        facesContext = null;
+        lifecycle = null;
+        lifecycleFactory = null;
+        renderKit = null;
+        request = null;
+        response = null;
+        servletContext = null;
+        session = null;
+        FactoryFinder.releaseFactories();
+        ResourceBundleVarNames.resetNames();
+
+        tearDownClassloader();
+    }
+    
+    protected void tearDownClassloader() throws Exception
+    {
+        if (classLoaderSet)
+        {
+            Thread.currentThread().setContextClassLoader(threadContextClassLoader);
+            threadContextClassLoader = null;
+            classLoaderSet = false;
+        }
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // Mock object instances for our tests
+    protected MockApplication application = null;
+    protected MockServletConfig config = null;
+    protected MockExternalContext externalContext = null;
+    protected MockFacesContext facesContext = null;
+    protected MockFacesContextFactory facesContextFactory = null;
+    protected MockLifecycle lifecycle = null;
+    protected MockLifecycleFactory lifecycleFactory = null;
+    protected MockRenderKit renderKit = null;
+    protected MockHttpServletRequest request = null;
+    protected MockHttpServletResponse response = null;
+    protected MockServletContext servletContext = null;
+    protected MockHttpSession session = null;
+
+    // Thread context class loader saved and restored after each test
+    private ClassLoader threadContextClassLoader = null;
+    private boolean classLoaderSet = false;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/AbstractViewControllerTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/AbstractViewControllerTestCase.java
new file mode 100644
index 0000000..aefc876
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/AbstractViewControllerTestCase.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.myfaces.test.base;
+
+import java.util.Iterator;
+
+/**
+ * <p>Abstract base class for testing <code>ViewController</code>
+ * implementations.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case.</p>
+ * 
+ * @since 1.0.0
+ */
+public abstract class AbstractViewControllerTestCase extends
+        AbstractJsfTestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Test case name
+     */
+    public AbstractViewControllerTestCase(String name)
+    {
+        super(name);
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    // ------------------------------------------------------- Protected Methods
+
+    /**
+     * <p>Test that the specified number of messages have been queued on the
+     * <code>FacesContext</code> instance, without regard to matching a
+     * particular client identifier.</p>
+     *
+     * @param expected The expected number of messages
+     */
+    protected void checkMessageCount(int expected)
+    {
+
+        int actual = 0;
+        Iterator messages = facesContext.getMessages();
+        while (messages.hasNext())
+        {
+            messages.next();
+            actual++;
+        }
+        assertEquals("Complete message count", expected, actual);
+
+    }
+
+    /**
+     * <p>Test that the specified number of messages have been queued on the
+     * <code>FacesContext</code> instance, for the specified client id.</p>
+     *
+     * @param clientId Client identifier of the component for which to
+     *  count queued messages
+     * @param expected The expected number of messages
+     */
+    protected void checkMessageCount(String clientId, int expected)
+    {
+
+        int actual = 0;
+        Iterator messages = facesContext.getMessages(clientId);
+        while (messages.hasNext())
+        {
+            messages.next();
+            actual++;
+        }
+        assertEquals("Complete message count", expected, actual);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMockTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMockTestCase.java
new file mode 100644
index 0000000..7f65067
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMockTestCase.java
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.base.junit4;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.FacesContextFactory;
+import javax.faces.lifecycle.Lifecycle;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.RenderKit;
+import javax.faces.render.RenderKitFactory;
+
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.mock.MockExternalContext;
+import org.apache.myfaces.test.mock.MockFacesContext;
+import org.apache.myfaces.test.mock.MockHttpServletRequest;
+import org.apache.myfaces.test.mock.MockHttpServletResponse;
+import org.apache.myfaces.test.mock.MockHttpSession;
+import org.apache.myfaces.test.mock.MockRenderKit;
+import org.apache.myfaces.test.mock.MockServletConfig;
+import org.apache.myfaces.test.mock.MockServletContext;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * <p>Abstract JUnit 4.5 test case base class, which sets up the JavaServer Faces
+ * mock object environment for a particular simulated request.  The following
+ * protected variables are initialized in the <code>setUp()</code> method, and
+ * cleaned up in the <code>tearDown()</code> method:</p>
+ * <ul>
+ * <li><code>application</code> (<code>Application</code>)</li>
+ * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+ * <li><code>externalContext</code> (<code>ExternalContext</code>)</li>
+ * <li><code>facesContext</code> (<code>FacesContext</code>)</li>
+ * <li><code>lifecycle</code> (<code>Lifecycle</code>)</li>
+ * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+ * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+ * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+ * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+ * </ul>
+ *
+ * <p>In addition, appropriate factory classes will have been registered with
+ * <code>javax.faces.FactoryFinder</code> for <code>Application</code> and
+ * <code>RenderKit</code> instances.  The created <code>FacesContext</code>
+ * instance will also have been registered in the proper thread local
+ * variable, to simulate what a servlet container would do.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case.</p>
+ * 
+ * @since 1.0.0
+ */
+
+public abstract class AbstractJsfConfigurableMockTestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of this test case
+     */
+    public AbstractJsfConfigurableMockTestCase()
+    {
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    /**
+     * <p>Set up instance variables required by this test case.</p>
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        // Set up a new thread context class loader
+        setUpClassloader();
+
+        // Set up Servlet API Objects
+        setUpServletObjects();
+
+        // Set up JSF API Objects
+        FactoryFinder.releaseFactories();
+
+        setFactories();
+
+        setUpJSFObjects();
+    }
+    
+    /**
+     * Set up the thread context classloader. JSF uses the this classloader
+     * in order to find related factory classes and other resources, but in
+     * some selected cases, the default classloader cannot be properly set.
+     * 
+     * @throws Exception 
+     */
+    protected void setUpClassloader() throws Exception
+    {
+        threadContextClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        Thread.currentThread()
+                .setContextClassLoader(
+                        new URLClassLoader(new URL[0], this.getClass()
+                                .getClassLoader()));
+        classLoaderSet = true;
+    }
+
+    /**
+     * <p>Setup JSF object used for the test. By default it calls to the following
+     * methods in this order:</p>
+     * 
+     * <ul>
+     * <li><code>setUpExternalContext();</code></li>
+     * <li><code>setUpLifecycle();</code></li>
+     * <li><code>setUpFacesContext();</code></li>
+     * <li><code>setUpView();</code></li>
+     * <li><code>setUpApplication();</code></li>
+     * <li><code>setUpRenderKit();</code></li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpJSFObjects() throws Exception
+    {
+        setUpExternalContext();
+        setUpLifecycle();
+        setUpFacesContext();
+        setUpView();
+        setUpApplication();
+        setUpRenderKit();
+    }
+
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+     * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpServletObjects() throws Exception
+    {
+        servletContext = new MockServletContext();
+        config = new MockServletConfig(servletContext);
+        session = new MockHttpSession();
+        session.setServletContext(servletContext);
+        request = new MockHttpServletRequest(session);
+        request.setServletContext(servletContext);
+        response = new MockHttpServletResponse();
+    }
+
+    /**
+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>
+     * 
+     * @throws Exception
+     */
+    protected void setFactories() throws Exception
+    {
+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
+                "org.apache.myfaces.test.mock.MockApplicationFactory");
+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockFacesContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,
+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");
+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,
+                "org.apache.myfaces.test.mock.MockRenderKitFactory");
+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,
+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");
+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.CLIENT_WINDOW_FACTORY,
+                "org.apache.myfaces.test.mock.MockClientWindowFactory");
+    }
+
+    /**
+     * Setup the <code>externalContext</code> variable, using the 
+     * servlet variables already initialized.
+     * 
+     * @throws Exception
+     */
+    protected void setUpExternalContext() throws Exception
+    {
+        externalContext = new MockExternalContext(servletContext, request,
+                response);
+    }
+
+    /**
+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>
+     * variables.
+     * 
+     * @throws Exception
+     */
+    protected void setUpLifecycle() throws Exception
+    {
+        lifecycleFactory = (LifecycleFactory) FactoryFinder
+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        lifecycle = lifecycleFactory
+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+    }
+
+    /**
+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>
+     * variable. Before end, by default it override <code>externalContext</code>
+     * variable from the value retrieved from facesContext.getExternalContext(),
+     * because sometimes it is possible facesContext overrides externalContext
+     * internally.
+     * 
+     * @throws Exception
+     */
+    protected void setUpFacesContext() throws Exception
+    {
+        facesContextFactory = (FacesContextFactory) FactoryFinder
+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+        facesContext = (FacesContext) facesContextFactory.getFacesContext(
+                servletContext, request, response, lifecycle);
+        if (facesContext.getExternalContext() != null)
+        {
+            externalContext = facesContext.getExternalContext();
+        }
+    }
+
+    /**
+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"
+     * and assign it to the current facesContext.
+     * 
+     * @throws Exception
+     */
+    protected void setUpView() throws Exception
+    {
+        UIViewRoot root = new UIViewRoot();
+        root.setViewId("/viewId");
+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        facesContext.setViewRoot(root);
+    }
+
+    /**
+     * Setup the <code>application</code> variable and before
+     * the end by default it is assigned to the <code>facesContext</code>
+     * variable, calling <code>facesContext.setApplication(application)</code>
+     * 
+     * @throws Exception
+     */
+    protected void setUpApplication() throws Exception
+    {
+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder
+                .getFactory(FactoryFinder.APPLICATION_FACTORY);
+        application = applicationFactory.getApplication();
+        ((MockFacesContext) facesContext).setApplication(application);
+    }
+
+    /**
+     * Setup the <code>renderKit</code> variable. This is a good place to use
+     * <code>ConfigParser</code> to register converters, validators, components
+     * or renderkits.
+     * 
+     * @throws Exception
+     */
+    protected void setUpRenderKit() throws Exception
+    {
+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+        renderKit = new MockRenderKit();
+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+                renderKit);
+    }
+
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+
+        application = null;
+        config = null;
+        externalContext = null;
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        facesContext = null;
+        lifecycle = null;
+        lifecycleFactory = null;
+        renderKit = null;
+        request = null;
+        response = null;
+        servletContext = null;
+        session = null;
+        FactoryFinder.releaseFactories();
+        ResourceBundleVarNames.resetNames();
+
+        tearDownClassloader();
+    }
+    
+    protected void tearDownClassloader() throws Exception
+    {
+        if (classLoaderSet)
+        {
+            Thread.currentThread().setContextClassLoader(threadContextClassLoader);
+            threadContextClassLoader = null;
+            classLoaderSet = false;
+        }
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // Mock object instances for our tests
+    protected Application application = null;
+    protected MockServletConfig config = null;
+    protected ExternalContext externalContext = null;
+    protected FacesContext facesContext = null;
+    protected FacesContextFactory facesContextFactory = null;
+    protected Lifecycle lifecycle = null;
+    protected LifecycleFactory lifecycleFactory = null;
+    protected RenderKit renderKit = null;
+    protected MockHttpServletRequest request = null;
+    protected MockHttpServletResponse response = null;
+    protected MockServletContext servletContext = null;
+    protected MockHttpSession session = null;
+
+    // Thread context class loader saved and restored after each test
+    private ClassLoader threadContextClassLoader = null;
+    private boolean classLoaderSet = false;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMultipleRequestsTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMultipleRequestsTestCase.java
new file mode 100644
index 0000000..6e52e3c
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMultipleRequestsTestCase.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.base.junit4;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.FacesContextFactory;
+import javax.faces.lifecycle.Lifecycle;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.RenderKit;
+import javax.faces.render.RenderKitFactory;
+
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.mock.MockExternalContext;
+import org.apache.myfaces.test.mock.MockFacesContext;
+import org.apache.myfaces.test.mock.MockFacesContextFactory;
+import org.apache.myfaces.test.mock.MockHttpServletRequest;
+import org.apache.myfaces.test.mock.MockHttpServletResponse;
+import org.apache.myfaces.test.mock.MockHttpSession;
+import org.apache.myfaces.test.mock.MockRenderKit;
+import org.apache.myfaces.test.mock.MockServletConfig;
+import org.apache.myfaces.test.mock.MockServletContext;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * <p>Abstract JUnit 4.5 test case base class, which sets up the JavaServer Faces
+ * mock object environment for multiple simulated request.  The following
+ * protected variables are initialized in the <code>setUp()</code> method, and
+ * cleaned up in the <code>tearDown()</code> method:</p>
+ * <ul>
+ * <li><code>application</code> (<code>Application</code>)</li>
+ * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+ * <li><code>externalContext</code> (<code>ExternalContext</code>)</li>
+ * <li><code>facesContext</code> (<code>FacesContext</code>)</li>
+ * <li><code>lifecycle</code> (<code>Lifecycle</code>)</li>
+ * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+ * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+ * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+ * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+ * </ul>
+ *
+ * <p>In addition, appropriate factory classes will have been registered with
+ * <code>javax.faces.FactoryFinder</code> for <code>Application</code> and
+ * <code>RenderKit</code> instances.  The created <code>FacesContext</code>
+ * instance will also have been registered in the proper thread local
+ * variable, to simulate what a servlet container would do.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case. Additionally, check on each test
+ * that setupRequest() and tearDownRequest() are called correctly.</p>
+ * 
+ * @since 1.0.3
+ */
+
+public abstract class AbstractJsfConfigurableMultipleRequestsTestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of this test case
+     */
+    public AbstractJsfConfigurableMultipleRequestsTestCase()
+    {
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    /**
+     * <p>Set up instance variables required by this test case.</p>
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        // Set up a new thread context class loader
+        setUpClassloader();
+
+        // Set up JSF Factories
+        FactoryFinder.releaseFactories();
+
+        setFactories();
+
+        // Setup servlet context and session
+        setUpServletContextAndSession();
+        
+        setUpLifecycle();
+        
+        setUpApplication();
+        
+        setUpRenderKit();
+    }
+    
+    /**
+     * Set up the thread context classloader. JSF uses the this classloader
+     * in order to find related factory classes and other resources, but in
+     * some selected cases, the default classloader cannot be properly set.
+     * 
+     * @throws Exception 
+     */
+    protected void setUpClassloader() throws Exception
+    {
+        threadContextClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        Thread.currentThread()
+                .setContextClassLoader(
+                        new URLClassLoader(new URL[0], this.getClass()
+                                .getClassLoader()));
+        classLoaderSet = true;
+    }
+
+    /**
+     * This method initialize an new empty request.
+     */
+    public void setupRequest() throws Exception
+    {
+        // Set up Servlet API Objects
+        setUpServletRequestAndResponse();
+
+        setUpJSFRequestObjects();
+    }
+
+    /**
+     * <p>Setup JSF object used for the test. By default it calls to the following
+     * methods in this order:</p>
+     * 
+     * <ul>
+     * <li><code>setUpExternalContext();</code></li>
+     * <li><code>setUpLifecycle();</code></li>
+     * <li><code>setUpFacesContext();</code></li>
+     * <li><code>setUpView();</code></li>
+     * <li><code>setUpApplication();</code></li>
+     * <li><code>setUpRenderKit();</code></li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpJSFRequestObjects() throws Exception
+    {
+        setUpExternalContext();
+        setUpFacesContext();
+        setUpView();
+    }
+
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+     * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpServletContextAndSession() throws Exception
+    {
+        servletContext = new MockServletContext();
+        config = new MockServletConfig(servletContext);
+        session = new MockHttpSession();
+        session.setServletContext(servletContext);
+    }
+
+    protected void setUpServletRequestAndResponse() throws Exception
+    {
+        request = new MockHttpServletRequest(session);
+        request.setServletContext(servletContext);
+        response = new MockHttpServletResponse();
+    }
+
+    /**
+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>
+     * 
+     * @throws Exception
+     */
+    protected void setFactories() throws Exception
+    {
+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
+                "org.apache.myfaces.test.mock.MockApplicationFactory");
+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockFacesContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,
+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");
+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,
+                "org.apache.myfaces.test.mock.MockRenderKitFactory");
+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,
+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");
+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.CLIENT_WINDOW_FACTORY,
+                "org.apache.myfaces.test.mock.MockClientWindowFactory");
+    }
+
+    /**
+     * Setup the <code>externalContext</code> variable, using the 
+     * servlet variables already initialized.
+     * 
+     * @throws Exception
+     */
+    protected void setUpExternalContext() throws Exception
+    {
+        externalContext = new MockExternalContext(servletContext, request,
+                response);
+    }
+
+    /**
+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>
+     * variables.
+     * 
+     * @throws Exception
+     */
+    protected void setUpLifecycle() throws Exception
+    {
+        lifecycleFactory = (MockLifecycleFactory) FactoryFinder
+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        lifecycle = (MockLifecycle) lifecycleFactory
+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+    }
+
+    /**
+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>
+     * variable. Before end, by default it override <code>externalContext</code>
+     * variable from the value retrieved from facesContext.getExternalContext(),
+     * because sometimes it is possible facesContext overrides externalContext
+     * internally.
+     * 
+     * @throws Exception
+     */
+    protected void setUpFacesContext() throws Exception
+    {
+        facesContextFactory = (MockFacesContextFactory) FactoryFinder
+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+        facesContext = (MockFacesContext) facesContextFactory.getFacesContext(
+                servletContext, request, response, lifecycle);
+        if (facesContext.getExternalContext() != null)
+        {
+            externalContext = (MockExternalContext) facesContext
+                    .getExternalContext();
+        }
+        if (facesContext instanceof MockFacesContext)
+        {
+            ((MockFacesContext) facesContext).setApplication(application);
+        }
+    }
+
+    /**
+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"
+     * and assign it to the current facesContext.
+     * 
+     * @throws Exception
+     */
+    protected void setUpView() throws Exception
+    {
+        UIViewRoot root = new UIViewRoot();
+        root.setViewId("/viewId");
+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        facesContext.setViewRoot(root);
+    }
+
+    /**
+     * Setup the <code>application</code> variable and before
+     * the end by default it is assigned to the <code>facesContext</code>
+     * variable, calling <code>facesContext.setApplication(application)</code>
+     * 
+     * @throws Exception
+     */
+    protected void setUpApplication() throws Exception
+    {
+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder
+                .getFactory(FactoryFinder.APPLICATION_FACTORY);
+        application = applicationFactory.getApplication();
+    }
+
+    /**
+     * Setup the <code>renderKit</code> variable. This is a good place to use
+     * <code>ConfigParser</code> to register converters, validators, components
+     * or renderkits.
+     * 
+     * @throws Exception
+     */
+    protected void setUpRenderKit() throws Exception
+    {
+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+        renderKit = new MockRenderKit();
+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+            renderKit);
+    }
+
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+
+        application = null;
+        config = null;
+        externalContext = null;
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        facesContext = null;
+        lifecycle = null;
+        lifecycleFactory = null;
+        renderKit = null;
+        request = null;
+        response = null;
+        servletContext = null;
+        session = null;
+        FactoryFinder.releaseFactories();
+        ResourceBundleVarNames.resetNames();
+
+        tearDownClassloader();
+    }
+    
+    protected void tearDownClassloader() throws Exception
+    {
+        if (classLoaderSet)
+        {
+            Thread.currentThread().setContextClassLoader(threadContextClassLoader);
+            threadContextClassLoader = null;
+            classLoaderSet = false;
+        }
+    }
+
+    
+    /**
+     * This method ends the current request.
+     */
+    public void tearDownRequest()
+    {
+        externalContext = null;
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        facesContext = null;
+        request = null;
+        response = null;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // Mock object instances for our tests
+    protected Application application = null;
+    protected MockServletConfig config = null;
+    protected ExternalContext externalContext = null;
+    protected FacesContext facesContext = null;
+    protected FacesContextFactory facesContextFactory = null;
+    protected Lifecycle lifecycle = null;
+    protected LifecycleFactory lifecycleFactory = null;
+    protected RenderKit renderKit = null;
+    protected MockHttpServletRequest request = null;
+    protected MockHttpServletResponse response = null;
+    protected MockServletContext servletContext = null;
+    protected MockHttpSession session = null;
+
+    // Thread context class loader saved and restored after each test
+    private ClassLoader threadContextClassLoader = null;
+    private boolean classLoaderSet = false;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfTestCase.java
new file mode 100644
index 0000000..78f6694
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfTestCase.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.base.junit4;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.component.UIViewRoot;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.RenderKitFactory;
+
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.mock.MockApplication;
+import org.apache.myfaces.test.mock.MockExternalContext;
+import org.apache.myfaces.test.mock.MockFacesContext;
+import org.apache.myfaces.test.mock.MockFacesContextFactory;
+import org.apache.myfaces.test.mock.MockHttpServletRequest;
+import org.apache.myfaces.test.mock.MockHttpServletResponse;
+import org.apache.myfaces.test.mock.MockHttpSession;
+import org.apache.myfaces.test.mock.MockRenderKit;
+import org.apache.myfaces.test.mock.MockServletConfig;
+import org.apache.myfaces.test.mock.MockServletContext;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * <p>Abstract JUnit 4.5 test case base class, which sets up the JavaServer Faces
+ * mock object environment for a particular simulated request.  The following
+ * protected variables are initialized in the <code>setUp()</code> method, and
+ * cleaned up in the <code>tearDown()</code> method:</p>
+ * <ul>
+ * <li><code>application</code> (<code>MockApplication</code>)</li>
+ * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+ * <li><code>externalContext</code> (<code>MockExternalContext</code>)</li>
+ * <li><code>facesContext</code> (<code>MockFacesContext</code>)</li>
+ * <li><code>lifecycle</code> (<code>MockLifecycle</code>)</li>
+ * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+ * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+ * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+ * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+ * </ul>
+ *
+ * <p>In addition, appropriate factory classes will have been registered with
+ * <code>javax.faces.FactoryFinder</code> for <code>Application</code> and
+ * <code>RenderKit</code> instances.  The created <code>FacesContext</code>
+ * instance will also have been registered in the apppriate thread local
+ * variable, to simulate what a servlet container would do.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case.</p>
+ * 
+ * @since 1.0.0
+ */
+
+public abstract class AbstractJsfTestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of this test case
+     */
+    public AbstractJsfTestCase()
+    {
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    /**
+     * <p>Set up instance variables required by this test case.</p>
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+
+        // Set up a new thread context class loader
+        setUpClassloader();
+
+        // Set up Servlet API Objects
+        setUpServletObjects();
+
+        // Set up JSF API Objects
+        FactoryFinder.releaseFactories();
+
+        setFactories();
+
+        setUpJSFObjects();
+    }
+
+    /**
+     * Set up the thread context classloader. JSF uses the this classloader
+     * in order to find related factory classes and other resources, but in
+     * some selected cases, the default classloader cannot be properly set.
+     * 
+     * @throws Exception 
+     */
+    protected void setUpClassloader() throws Exception
+    {
+        threadContextClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        Thread.currentThread()
+                .setContextClassLoader(
+                        new URLClassLoader(new URL[0], this.getClass()
+                                .getClassLoader()));
+        classLoaderSet = true;
+    }
+
+    /**
+     * <p>Setup JSF object used for the test. By default it calls to the following
+     * methods in this order:</p>
+     * 
+     * <ul>
+     * <li><code>setUpExternalContext();</code></li>
+     * <li><code>setUpLifecycle();</code></li>
+     * <li><code>setUpFacesContext();</code></li>
+     * <li><code>setUpView();</code></li>
+     * <li><code>setUpApplication();</code></li>
+     * <li><code>setUpRenderKit();</code></li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpJSFObjects() throws Exception
+    {
+        setUpExternalContext();
+        setUpLifecycle();
+        setUpFacesContext();
+        setUpView();
+        setUpApplication();
+        setUpRenderKit();
+    }
+
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+     * <li><code>session</code> (<code>MockHttpSession</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpServletObjects() throws Exception
+    {
+        servletContext = new MockServletContext();
+        config = new MockServletConfig(servletContext);
+        session = new MockHttpSession();
+        session.setServletContext(servletContext);
+        request = new MockHttpServletRequest(session);
+        request.setServletContext(servletContext);
+        response = new MockHttpServletResponse();
+    }
+
+    /**
+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>
+     * 
+     * @throws Exception
+     */
+    protected void setFactories() throws Exception
+    {
+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
+                "org.apache.myfaces.test.mock.MockApplicationFactory");
+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockFacesContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,
+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");
+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,
+                "org.apache.myfaces.test.mock.MockRenderKitFactory");
+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,
+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");
+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.CLIENT_WINDOW_FACTORY,
+                "org.apache.myfaces.test.mock.MockClientWindowFactory");
+    }
+
+    /**
+     * Setup the <code>externalContext</code> variable, using the 
+     * servlet variables already initialized.
+     * 
+     * @throws Exception
+     */
+    protected void setUpExternalContext() throws Exception
+    {
+        externalContext = new MockExternalContext(servletContext, request,
+                response);
+    }
+
+    /**
+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>
+     * variables.
+     * 
+     * @throws Exception
+     */
+    protected void setUpLifecycle() throws Exception
+    {
+        lifecycleFactory = (MockLifecycleFactory) FactoryFinder
+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        lifecycle = (MockLifecycle) lifecycleFactory
+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+    }
+
+    /**
+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>
+     * variable. Before end, by default it override <code>externalContext</code>
+     * variable from the value retrieved from facesContext.getExternalContext(),
+     * because sometimes it is possible facesContext overrides externalContext
+     * internally.
+     * 
+     * @throws Exception
+     */
+    protected void setUpFacesContext() throws Exception
+    {
+        facesContextFactory = (MockFacesContextFactory) FactoryFinder
+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+        facesContext = (MockFacesContext) facesContextFactory.getFacesContext(
+                servletContext, request, response, lifecycle);
+        if (facesContext.getExternalContext() != null)
+        {
+            externalContext = (MockExternalContext) facesContext
+                    .getExternalContext();
+        }
+    }
+
+    /**
+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"
+     * and assign it to the current facesContext.
+     * 
+     * @throws Exception
+     */
+    protected void setUpView() throws Exception
+    {
+        UIViewRoot root = new UIViewRoot();
+        root.setViewId("/viewId");
+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        facesContext.setViewRoot(root);
+    }
+
+    /**
+     * Setup the <code>application</code> variable and before
+     * the end by default it is assigned to the <code>facesContext</code>
+     * variable, calling <code>facesContext.setApplication(application)</code>
+     * 
+     * @throws Exception
+     */
+    protected void setUpApplication() throws Exception
+    {
+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder
+                .getFactory(FactoryFinder.APPLICATION_FACTORY);
+        application = (MockApplication) applicationFactory.getApplication();
+        facesContext.setApplication(application);
+    }
+
+    /**
+     * Setup the <code>renderKit</code> variable. This is a good place to use
+     * <code>ConfigParser</code> to register converters, validators, components
+     * or renderkits.
+     * 
+     * @throws Exception
+     */
+    protected void setUpRenderKit() throws Exception
+    {
+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+        renderKit = new MockRenderKit();
+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+                renderKit);
+    }
+
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+
+        application = null;
+        config = null;
+        externalContext = null;
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        facesContext = null;
+        lifecycle = null;
+        lifecycleFactory = null;
+        renderKit = null;
+        request = null;
+        response = null;
+        servletContext = null;
+        session = null;
+        FactoryFinder.releaseFactories();
+        ResourceBundleVarNames.resetNames();
+
+        tearDownClassloader();
+
+    }
+    
+    protected void tearDownClassloader() throws Exception
+    {
+        if (classLoaderSet)
+        {
+            Thread.currentThread().setContextClassLoader(threadContextClassLoader);
+            threadContextClassLoader = null;
+            classLoaderSet = false;
+        }
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // Mock object instances for our tests
+    protected MockApplication application = null;
+    protected MockServletConfig config = null;
+    protected MockExternalContext externalContext = null;
+    protected MockFacesContext facesContext = null;
+    protected MockFacesContextFactory facesContextFactory = null;
+    protected MockLifecycle lifecycle = null;
+    protected MockLifecycleFactory lifecycleFactory = null;
+    protected MockRenderKit renderKit = null;
+    protected MockHttpServletRequest request = null;
+    protected MockHttpServletResponse response = null;
+    protected MockServletContext servletContext = null;
+    protected MockHttpSession session = null;
+
+    // Thread context class loader saved and restored after each test
+    private ClassLoader threadContextClassLoader = null;
+    private boolean classLoaderSet = false;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractViewControllerTestCase.java b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractViewControllerTestCase.java
new file mode 100644
index 0000000..2f2e7c2
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/base/junit4/AbstractViewControllerTestCase.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.myfaces.test.base.junit4;
+
+import java.util.Iterator;
+
+import org.junit.Assert;
+
+/**
+ * <p>Abstract base class for testing <code>ViewController</code>
+ * implementations.</p>
+ *
+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure
+ * your <code>setUp()</code> and <code>tearDown()</code> methods call
+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,
+ * and that you implement your own <code>suite()</code> method that exposes
+ * the test methods for your test case.</p>
+ * 
+ * @since 1.0.0
+ */
+public abstract class AbstractViewControllerTestCase extends
+        AbstractJsfTestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Test case name
+     */
+    public AbstractViewControllerTestCase()
+    {
+        super();
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    // ------------------------------------------------------- Protected Methods
+
+    /**
+     * <p>Test that the specified number of messages have been queued on the
+     * <code>FacesContext</code> instance, without regard to matching a
+     * particular client identifier.</p>
+     *
+     * @param expected The expected number of messages
+     */
+    protected void checkMessageCount(int expected)
+    {
+
+        int actual = 0;
+        Iterator messages = facesContext.getMessages();
+        while (messages.hasNext())
+        {
+            messages.next();
+            actual++;
+        }
+        Assert.assertEquals("Complete message count", expected, actual);
+
+    }
+
+    /**
+     * <p>Test that the specified number of messages have been queued on the
+     * <code>FacesContext</code> instance, for the specified client id.</p>
+     *
+     * @param clientId Client identifier of the component for which to
+     *  count queued messages
+     * @param expected The expected number of messages
+     */
+    protected void checkMessageCount(String clientId, int expected)
+    {
+
+        int actual = 0;
+        Iterator messages = facesContext.getMessages(clientId);
+        while (messages.hasNext())
+        {
+            messages.next();
+            actual++;
+        }
+        Assert.assertEquals("Complete message count", expected, actual);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/cargo/CargoTestSetup.java b/test30/src/main/java/org/apache/myfaces/test/cargo/CargoTestSetup.java
new file mode 100644
index 0000000..103fe4f
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/cargo/CargoTestSetup.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.myfaces.test.cargo;
+
+import java.io.File;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+
+import org.codehaus.cargo.container.InstalledLocalContainer;
+import org.codehaus.cargo.container.ContainerType;
+import org.codehaus.cargo.container.tomcat.Tomcat5xInstalledLocalContainer;
+import org.codehaus.cargo.container.deployable.Deployable;
+import org.codehaus.cargo.container.deployable.DeployableType;
+import org.codehaus.cargo.container.configuration.LocalConfiguration;
+import org.codehaus.cargo.container.configuration.ConfigurationType;
+import org.codehaus.cargo.generic.deployable.DefaultDeployableFactory;
+import org.codehaus.cargo.generic.configuration.ConfigurationFactory;
+import org.codehaus.cargo.generic.configuration.DefaultConfigurationFactory;
+import org.codehaus.cargo.generic.DefaultContainerFactory;
+import org.codehaus.cargo.util.log.FileLogger;
+
+/**
+ * <p>Convenience <code>TestSetup</code> class which uses Cargo to start
+ * and stop a Servlet container.</p>
+ * 
+ * @since 1.0.0
+ */
+public class CargoTestSetup extends TestSetup
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test setup.</p>
+     *
+     * @param test Tests to be run within this test setup.
+     */
+    public CargoTestSetup(Test test)
+    {
+        super(test);
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The installed local container for this test setup.</p>
+     */
+    private InstalledLocalContainer container;
+
+    // ------------------------------------------------------ Test Setup Methods
+
+    /**
+     * <p>Start the container prior to running the tests.</p>
+     * <p>The following System properties are used:
+     * <ul>
+     * <li>cargo.container.id - ID of the container to use. [tomcat5x]</li>
+     * <li>cargo.container.home - Full path to a local installation of the container.
+     * If not set, uses the value of the TOMCAT_HOME environment variable.
+     * One of cargo.container.home or TOMCAT_HOME is REQUIRED.</li>
+     * <li>cargo.deployable - Full path to the war file to deploy. REQUIRED.</li>
+     * <li>cargo.container.output - Full path to a file to use for output. [none]</li>
+     * <li>cargo.container.log - Full path to a file to use for logging. [none]</li>
+     * <li>cargo.servlet.port - The port on which the container should listen. [8080]</li>
+     * </ul>
+     * </p>
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void setUp() throws Exception
+    {
+
+        super.setUp();
+
+        // If there is no container id, default to Tomcat 5x
+        String containerId = System.getProperty("cargo.container.id");
+        if (containerId == null)
+        {
+            containerId = Tomcat5xInstalledLocalContainer.ID;
+        }
+        System.out.println("[INFO] container id: " + containerId);
+
+        // Construct the war, using the container id and the path to the war file
+        String deployablePath = System.getProperty("cargo.deployable");
+        System.out.println("[INFO] deployable: " + deployablePath);
+        Deployable war = new DefaultDeployableFactory().createDeployable(
+                containerId, deployablePath, DeployableType.WAR);
+
+        // Container configuration
+        ConfigurationFactory configurationFactory = new DefaultConfigurationFactory();
+
+        LocalConfiguration configuration = (LocalConfiguration) configurationFactory
+                .createConfiguration(containerId, ConfigurationType.STANDALONE);
+
+        // Find and (if provided) set the port to use for the container.
+        String servletPort = System.getProperty("cargo.servlet.port");
+        if (servletPort != null)
+        {
+            configuration.setProperty("cargo.servlet.port", servletPort);
+            System.out.println("[INFO] servlet port: " + servletPort);
+        }
+
+        configuration.addDeployable(war);
+
+        container = (InstalledLocalContainer) new DefaultContainerFactory()
+                .createContainer(containerId, ContainerType.INSTALLED,
+                        configuration);
+
+        // If 'cargo.container.home' is not set, or if an expression was
+        // passed through, try to use the TOMCAT_HOME environment variable.
+        String containerHome = System.getProperty("cargo.container.home");
+        if (containerHome == null || containerHome.startsWith("$"))
+        {
+            containerHome = System.getenv("TOMCAT_HOME");
+        }
+        System.out.println("[INFO] container home: " + containerHome);
+        container.setHome(new File(containerHome));
+
+        // Find and (if provided) set the path to a log file
+        String containerLog = System.getProperty("cargo.container.log");
+        if (containerLog != null)
+        {
+            System.out.println("[INFO] container log: " + containerLog);
+            container.setLogger(new FileLogger(containerLog, false));
+        }
+
+        // Find and (if provided) set the path to an output file
+        String containerOutput = System.getProperty("cargo.container.output");
+        if (containerOutput != null)
+        {
+            System.out.println("[INFO] container output: " + containerOutput);
+            container.setOutput(new File(containerOutput));
+        }
+
+        container.start();
+    }
+
+    /**
+     * Stop the container after running the tests.
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void tearDown() throws Exception
+    {
+        container.stop();
+        super.tearDown();
+    }
+
+    /**
+     * Return the name of the test setup.
+     * (Temporarily required due to MSUREFIRE-119.)
+     *
+     * @return the name of the test setup.
+     * @deprecated No replacement.
+     */
+
+    public String getName()
+    {
+        return "CargoTestSetup";
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/config/ConfigParser.java b/test30/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
new file mode 100644
index 0000000..b0ce0d6
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
@@ -0,0 +1,709 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.config;

+

+import java.io.IOException;

+import java.net.URL;

+

+import javax.faces.FactoryFinder;

+import javax.faces.application.Application;

+import javax.faces.application.ApplicationFactory;

+import javax.faces.render.ClientBehaviorRenderer;

+import javax.faces.render.RenderKit;

+import javax.faces.render.RenderKitFactory;

+import javax.faces.render.Renderer;

+

+import org.apache.commons.digester.Digester;

+import org.apache.commons.digester.Rule;

+import org.apache.myfaces.test.mock.MockRenderKit;

+import org.xml.sax.Attributes;

+import org.xml.sax.SAXException;

+

+/**

+ * <p>Utility class to parse JavaServer Faces configuration resources, and

+ * register JSF artifacts with the mock object hierarchy.</p>

+ *

+ * <p>The following artifacts are registered:</p>

+ * <ul>

+ *     <li><code>Converter</code> (by-id and by-class)</li>

+ *     <li><code>RenderKit</code> and <code>Renderer</code>,<code>ClientBehaviorRenderer</code></li>

+ *     <li><code>UIComponent</code></li>

+ *     <li><code>Validator</code></li>

+ *     <li><code>Behavior</code></li>

+ * </ul>

+ *

+ * <p>Note that any declared <em>factory</em> instances are explicitly

+ * <strong>NOT</strong> registered, allowing the mock object hierarchy

+ * of the Myfaces Test Framework to manage these APIs.</p>

+ *

+ * <p><strong>USAGE NOTE</strong> - If you are using an instance of this

+ * class within a subclass of <code>AbstractJsfTestCase</code> or

+ * <code>AbstractJmockJsfTestCase</code>, be sure you have completed the

+ * <code>setUp()</code> processing in this base class before calling one

+ * of the <code>parse()</code> methods.</p>

+ *

+ * @since 1.0.0

+ */

+public class ConfigParser

+{

+

+    // ------------------------------------------------------------ Constructors

+

+    /** Creates a new instance of ConfigParser */

+    public ConfigParser()

+    {

+    }

+

+    // ------------------------------------------------------ Manifest Constants

+

+    /**

+     * <p>Configuration resource URLs for the JSF RI.</p>

+     */

+    private static final String[] JSFRI_RESOURCES = { "/com/sun/faces/jsf-ri-runtime.xml", };

+

+    /**

+     * <p>Configuration resource URLs for Apache MyFaces.</p>

+     */

+    private static final String[] MYFACES_RESOURCES = { "/org/apache/myfaces/resource/standard-faces-config.xml", };

+

+    /**

+     * <p>Configuration resource URLs for Apache MyFaces 1.2.</p>

+     */

+    private static final String[] MYFACES_RESOURCES12 = { "/META-INF/standard-faces-config.xml", };

+

+    // ------------------------------------------------------ Instance Variables

+

+    /**

+     * <p>The <code>Digester</code> instance we will use for parsing.</p>

+     */

+    private Digester digester = null;

+

+    // ------------------------------------------------------- Public Properties

+

+    /**

+     * <p>Return the URLs of the platform configuration resources for this

+     * application.  The following platforms are currently supported:</p>

+     * <ul>

+     * <li>JavaServer Faces Reference Implementation (version 1.0 - 1.2)</li>

+     * <li>MyFaces (version 1.1)</li>

+     * </ul>

+     *

+     * <p>If MyFaces (version 1.2), currently under development, does not change

+     * the name of the configuration resource, it will be supported as well.</p>

+     */

+    public URL[] getPlatformURLs()

+    {

+

+        URL[] urls = translate(JSFRI_RESOURCES);

+        if (urls[0] == null)

+        {

+            urls = translate(MYFACES_RESOURCES12);

+            if (urls[0] == null)

+            {

+                urls = translate(MYFACES_RESOURCES);

+            }

+        }

+        return urls;

+

+    }

+

+    // ---------------------------------------------------------- Public Methods

+

+    /**

+     * <p>Parse the specified JavaServer Faces configuration resource, causing

+     * the appropriate JSF artifacts to be registered with the mock object

+     * hierarchy.</p>

+     *

+     * @param url <code>URL</code> of the configuration resource to parse

+     *

+     * @exception IOException if an input/output error occurs

+     * @exception SAXException if a parsing error occurs

+     */

+    public void parse(URL url) throws IOException, SAXException

+    {

+

+        // Acquire and configure the Digester instance we will use

+        Digester digester = digester();

+        ApplicationFactory factory = (ApplicationFactory) FactoryFinder

+                .getFactory(FactoryFinder.APPLICATION_FACTORY);

+        Application application = factory.getApplication();

+        digester.push(application);

+

+        // Perform the required parsing

+        try

+        {

+            digester.parse(url);

+        }

+        finally

+        {

+            digester.clear();

+        }

+

+    }

+

+    /**

+     * <p>Parse the specified set of JavaServer Faces configuration resources,

+     * in the listed order, causing the appropriate JSF artifacts to be registered

+     * with the mock object hierarchy.</p>

+     *

+     * @param urls <code>URL</code>s of the configuration resources to parse

+     *

+     * @exception IOException if an input/output error occurs

+     * @exception SAXException if a parsing error occurs

+     */

+    public void parse(URL[] urls) throws IOException, SAXException

+    {

+

+        for (int i = 0; i < urls.length; i++)

+        {

+            parse(urls[i]);

+        }

+

+    }

+

+    // --------------------------------------------------------- Private Methods

+

+    /**

+     * <p>Return the <code>Digester</code> instance we will use for parsing,

+     * creating and configuring a new instance if necessary.</p>

+     */

+    private Digester digester()

+    {

+

+        if (this.digester == null)

+        {

+            this.digester = new Digester();

+            digester.addRule("faces-config/component", new ComponentRule());

+            digester.addCallMethod("faces-config/component/component-type",

+                    "setComponentType", 0);

+            digester.addCallMethod("faces-config/component/component-class",

+                    "setComponentClass", 0);

+            digester.addRule("faces-config/converter", new ConverterRule());

+            digester.addCallMethod("faces-config/converter/converter-id",

+                    "setConverterId", 0);

+            digester.addCallMethod("faces-config/converter/converter-class",

+                    "setConverterClass", 0);

+            digester.addCallMethod(

+                    "faces-config/converter/converter-for-class",

+                    "setConverterForClass", 0);

+            digester.addRule("faces-config/render-kit", new RenderKitRule());

+            digester.addRule("faces-config/render-kit/render-kit-id",

+                    new RenderKitIdRule());

+            digester.addRule("faces-config/render-kit/renderer",

+                    new RendererRule());

+            digester.addCallMethod(

+                    "faces-config/render-kit/renderer/component-family",

+                    "setComponentFamily", 0);

+            digester.addCallMethod(

+                    "faces-config/render-kit/renderer/renderer-class",

+                    "setRendererClass", 0);

+            digester.addCallMethod(

+                    "faces-config/render-kit/renderer/renderer-type",

+                    "setRendererType", 0);

+            digester.addRule(

+                    "faces-config/render-kit/client-behavior-renderer",

+                    new ClientBehaviorRendererRule());

+            digester

+                    .addCallMethod(

+                            "faces-config/render-kit/client-behavior-renderer/client-behavior-renderer-type",

+                            "setClientBehaviorRendererType", 0);

+            digester

+                    .addCallMethod(

+                            "faces-config/render-kit/client-behavior-renderer/client-behavior-renderer-class",

+                            "setClientBehaviorRendererClass", 0);

+            digester.addRule("faces-config/validator", new ValidatorRule());

+            digester.addCallMethod("faces-config/validator/validator-id",

+                    "setValidatorId", 0);

+            digester.addCallMethod("faces-config/validator/validator-class",

+                    "setValidatorClass", 0);

+            digester.addRule("faces-config/behavior", new BehaviorRule());

+            digester.addCallMethod("faces-config/behavior/behavior-id",

+                    "setBehaviorId", 0);

+            digester.addCallMethod("faces-config/behavior/behavior-class",

+                    "setBehaviorClass", 0);

+        }

+        return this.digester;

+

+    }

+

+    /**

+     * <p>Translate an array of resource names into an array of resource URLs.</p>

+     *

+     * @param names Resource names to translate

+     */

+    private URL[] translate(String[] names)

+    {

+

+        URL[] results = new URL[names.length];

+        for (int i = 0; i < names.length; i++)

+        {

+            results[i] = this.getClass().getResource(names[i]);

+        }

+        return results;

+

+    }

+

+    // --------------------------------------------------------- Private Classes

+

+    /**

+     * <p>Data bean that stores information related to a component.</p>

+     */

+    class ComponentBean

+    {

+

+        private String componentClass;

+

+        public String getComponentClass()

+        {

+            return this.componentClass;

+        }

+

+        public void setComponentClass(String componentClass)

+        {

+            this.componentClass = componentClass;

+        }

+

+        private String componentType;

+

+        public String getComponentType()

+        {

+            return this.componentType;

+        }

+

+        public void setComponentType(String componentType)

+        {

+            this.componentType = componentType;

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing components.</p>

+     */

+    class ComponentRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new ComponentBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            ComponentBean bean = (ComponentBean) getDigester().pop();

+            Application application = (Application) getDigester().peek();

+            application.addComponent(bean.getComponentType(), bean

+                    .getComponentClass());

+        }

+

+    }

+

+    /**

+     * <p>Data bean that stores information related to a converter.</p>

+     */

+    class ConverterBean

+    {

+

+        private String converterClass;

+

+        public String getConverterClass()

+        {

+            return this.converterClass;

+        }

+

+        public void setConverterClass(String converterClass)

+        {

+            this.converterClass = converterClass;

+        }

+

+        private String converterForClass;

+

+        public String getConverterForClass()

+        {

+            return this.converterForClass;

+        }

+

+        public void setConverterForClass(String converterForClass)

+        {

+            this.converterForClass = converterForClass;

+        }

+

+        private String converterId;

+

+        public String getConverterId()

+        {

+            return this.converterId;

+        }

+

+        public void setConverterId(String converterId)

+        {

+            this.converterId = converterId;

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing converers.</p>

+     */

+    class ConverterRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new ConverterBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            ConverterBean bean = (ConverterBean) getDigester().pop();

+            Application application = (Application) getDigester().peek();

+            if (bean.getConverterId() != null)

+            {

+                application.addConverter(bean.getConverterId(), bean

+                        .getConverterClass());

+            }

+            else

+            {

+                Class clazz = null;

+                try

+                {

+                    clazz = classForName(bean.getConverterForClass());

+                }

+                catch (ClassNotFoundException e)

+                {

+                    throw new IllegalArgumentException(

+                            "java.lang.ClassNotFoundException: "

+                                    + bean.getConverterForClass());

+                }

+                application.addConverter(clazz, bean.getConverterClass());

+            }

+        }

+

+    }

+

+    private Class classForName(String type) throws ClassNotFoundException

+    {

+        try

+        {

+            // Try WebApp ClassLoader first

+            return Class.forName(type, false, // do not initialize for faster startup

+                    Thread.currentThread().getContextClassLoader());

+        }

+        catch (ClassNotFoundException ignore)

+        {

+            // fallback: Try ClassLoader for ClassUtils (i.e. the myfaces.jar lib)

+            return Class.forName(type, false, // do not initialize for faster startup

+                    this.getClass().getClassLoader());

+        }

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing render kits.</p>

+     */

+    class RenderKitRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            RenderKitFactory factory = (RenderKitFactory) FactoryFinder

+                    .getFactory(FactoryFinder.RENDER_KIT_FACTORY);

+            getDigester().push(

+                    factory.getRenderKit(null,

+                            RenderKitFactory.HTML_BASIC_RENDER_KIT));

+        }

+

+        public void end(String namespace, String name)

+        {

+            getDigester().pop();

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing render kit identifiers.</p>

+     */

+    class RenderKitIdRule extends Rule

+    {

+

+        public void body(String namespace, String name, String text)

+        {

+            String renderKitId = text.trim();

+            RenderKitFactory factory = (RenderKitFactory) FactoryFinder

+                    .getFactory(FactoryFinder.RENDER_KIT_FACTORY);

+            RenderKit renderKit = factory.getRenderKit(null, renderKitId);

+            if (renderKit == null)

+            {

+                renderKit = new MockRenderKit();

+                factory.addRenderKit(renderKitId, renderKit);

+            }

+            getDigester().pop();

+            getDigester().push(renderKit);

+        }

+

+    }

+

+    /**

+     * <p>Data bean that stores information related to a renderer.</p>

+     */

+    class RendererBean

+    {

+

+        private String componentFamily;

+

+        public String getComponentFamily()

+        {

+            return this.componentFamily;

+        }

+

+        public void setComponentFamily(String componentFamily)

+        {

+            this.componentFamily = componentFamily;

+        }

+

+        private String rendererClass;

+

+        public String getRendererClass()

+        {

+            return this.rendererClass;

+        }

+

+        public void setRendererClass(String rendererClass)

+        {

+            this.rendererClass = rendererClass;

+        }

+

+        private String rendererType;

+

+        public String getRendererType()

+        {

+            return this.rendererType;

+        }

+

+        public void setRendererType(String rendererType)

+        {

+            this.rendererType = rendererType;

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing renderers.</p>

+     */

+    class RendererRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new RendererBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            RendererBean bean = (RendererBean) getDigester().pop();

+            RenderKit kit = (RenderKit) getDigester().peek();

+            Renderer renderer = null;

+            Class clazz = null;

+            try

+            {

+                clazz = classForName(bean.getRendererClass());

+                renderer = (Renderer) clazz.newInstance();

+            }

+            catch (Exception e)

+            {

+                throw new IllegalArgumentException(

+                        "Exception while trying to instantiate"

+                                + " renderer class '" + bean.getRendererClass()

+                                + "' : " + e.getMessage());

+            }

+            kit.addRenderer(bean.getComponentFamily(), bean.getRendererType(),

+                    renderer);

+        }

+

+    }

+

+    /**

+     * <p>Data bean that stores information related to a validator.</p>

+     */

+    class ValidatorBean

+    {

+

+        private String validatorClass;

+

+        public String getValidatorClass()

+        {

+            return this.validatorClass;

+        }

+

+        public void setValidatorClass(String validatorClass)

+        {

+            this.validatorClass = validatorClass;

+        }

+

+        private String validatorId;

+

+        public String getValidatorId()

+        {

+            return this.validatorId;

+        }

+

+        public void setValidatorId(String validatorId)

+        {

+            this.validatorId = validatorId;

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing validators.</p>

+     */

+    class ValidatorRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new ValidatorBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            ValidatorBean bean = (ValidatorBean) getDigester().pop();

+            Application application = (Application) getDigester().peek();

+            application.addValidator(bean.getValidatorId(), bean

+                    .getValidatorClass());

+        }

+

+    }

+

+    class ClientBehaviorRendererBean

+    {

+        private String clientBehaviorRendererType;

+

+        private String clientBehaviorRendererClass;

+

+        public String getClientBehaviorRendererType()

+        {

+            return clientBehaviorRendererType;

+        }

+

+        public void setClientBehaviorRendererType(

+                String clientBehaviorRendererType)

+        {

+            this.clientBehaviorRendererType = clientBehaviorRendererType;

+        }

+

+        public String getClientBehaviorRendererClass()

+        {

+            return clientBehaviorRendererClass;

+        }

+

+        public void setClientBehaviorRendererClass(

+                String clientBehaviorRendererClass)

+        {

+            this.clientBehaviorRendererClass = clientBehaviorRendererClass;

+        }

+    }

+

+    class ClientBehaviorRendererRule extends Rule

+    {

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new ClientBehaviorRendererBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            ClientBehaviorRendererBean bean = (ClientBehaviorRendererBean) getDigester()

+                    .pop();

+            RenderKit kit = (RenderKit) getDigester().peek();

+            ClientBehaviorRenderer renderer = null;

+            Class clazz = null;

+            try

+            {

+                clazz = classForName(bean.getClientBehaviorRendererClass());

+                renderer = (ClientBehaviorRenderer) clazz.newInstance();

+            }

+            catch (Exception e)

+            {

+                throw new IllegalArgumentException(

+                        "Exception while trying to instantiate"

+                                + " client behavior renderer class '"

+                                + bean.getClientBehaviorRendererClass()

+                                + "' : " + e.getMessage());

+            }

+            kit.addClientBehaviorRenderer(bean.getClientBehaviorRendererType(),

+                    renderer);

+        }

+    }

+

+    /**

+     * <p>Data bean that stores information related to a behavior.</p>

+     */

+    class BehaviorBean

+    {

+

+        private String behaviorClass;

+

+        public String getBehaviorClass()

+        {

+            return this.behaviorClass;

+        }

+

+        @SuppressWarnings("unused")

+        public void setBehaviorClass(String behaviorClass)

+        {

+            this.behaviorClass = behaviorClass;

+        }

+

+        private String behaviorId;

+

+        public String getBehaviorId()

+        {

+            return this.behaviorId;

+        }

+

+        @SuppressWarnings("unused")

+        public void setBehaviorId(String behaviorId)

+        {

+            this.behaviorId = behaviorId;

+        }

+

+    }

+

+    /**

+     * <p>Digester <code>Rule</code> for processing behaviors.</p>

+     */

+    class BehaviorRule extends Rule

+    {

+

+        public void begin(String namespace, String name, Attributes attributes)

+        {

+            getDigester().push(new BehaviorBean());

+        }

+

+        public void end(String namespace, String name)

+        {

+            BehaviorBean bean = (BehaviorBean) getDigester().pop();

+            Application application = (Application) getDigester().peek();

+            application.addBehavior(bean.getBehaviorId(), bean

+                    .getBehaviorClass());

+        }

+

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/config/ResourceBundleVarNames.java b/test30/src/main/java/org/apache/myfaces/test/config/ResourceBundleVarNames.java
new file mode 100644
index 0000000..2edc340
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/config/ResourceBundleVarNames.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.myfaces.test.config;

+

+import java.util.HashMap;

+import java.util.Map;

+

+/**

+ * @author Rudy De Busscher

+ */

+public final class ResourceBundleVarNames

+{

+

+    private static Map<String, String> varNames = new HashMap<String, String>();

+

+    private ResourceBundleVarNames()

+    {

+    }

+

+    public static void addVarName(String varName, String fullyQualifiedBaseName)

+    {

+        varNames.put(varName, fullyQualifiedBaseName);

+    }

+

+    public static String getVarName(String varName)

+    {

+        return varNames.get(varName);

+    }

+

+    public static void resetNames()

+    {

+        varNames.clear();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/el/AbstractELResolver.java b/test30/src/main/java/org/apache/myfaces/test/el/AbstractELResolver.java
new file mode 100644
index 0000000..6ec43c2
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/AbstractELResolver.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.myfaces.test.el;
+
+import java.beans.FeatureDescriptor;
+import javax.el.ELResolver;
+
+/**
+ * <p>Convenience base class for EL resolvers.</p>
+ * 
+ * @since 1.0.0
+ */
+abstract class AbstractELResolver extends ELResolver
+{
+
+    // ------------------------------------------------------- Protected Methods
+
+    /**
+     * <p>Create and return a <code>FeatureDescriptor</code> configured with
+     * the specified arguments.</p>
+     *
+     * @param name Feature name
+     * @param displayName Display name
+     * @param description Short description
+     * @param expert Flag indicating this feature is for experts
+     * @param hidden Flag indicating this feature should be hidden
+     * @param preferred Flag indicating this feature is the preferred one
+     *  among features of the same type
+     * @param type Runtime type of this feature
+     * @param designTime Flag indicating feature is resolvable at design time
+     */
+    protected FeatureDescriptor descriptor(String name, String displayName,
+            String description, boolean expert, boolean hidden,
+            boolean preferred, Object type, boolean designTime)
+    {
+
+        FeatureDescriptor descriptor = new FeatureDescriptor();
+
+        descriptor.setName(name);
+        descriptor.setDisplayName(displayName);
+        descriptor.setShortDescription(description);
+        descriptor.setExpert(expert);
+        descriptor.setHidden(hidden);
+        descriptor.setPreferred(preferred);
+        descriptor.setValue(ELResolver.TYPE, type);
+        if (designTime)
+        {
+            descriptor.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME,
+                    Boolean.TRUE);
+        }
+        else
+        {
+            descriptor.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME,
+                    Boolean.FALSE);
+        }
+
+        return descriptor;
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/ExpressionTokenizer.java b/test30/src/main/java/org/apache/myfaces/test/el/ExpressionTokenizer.java
new file mode 100644
index 0000000..6798cec
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/ExpressionTokenizer.java
@@ -0,0 +1,193 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.el;

+

+import java.io.IOException;

+import java.io.StringWriter;

+import java.text.ParsePosition;

+import java.util.ArrayList;

+

+/**

+ * Expression Tokenizer

+ */

+class ExpressionTokenizer

+{

+    private ExpressionTokenizer()

+    {

+    }

+

+    public static String[] tokenize(CharSequence expr)

+    {

+        final ArrayList<String> tokens = new ArrayList<String>();

+        ParsePosition pos = new ParsePosition(0);

+        int len = expr.length();

+        boolean sep = true;

+        while (pos.getIndex() < len)

+        {

+            int here = pos.getIndex();

+            char c = expr.charAt(here);

+            switch (c)

+            {

+            case ' ':

+                next(pos);

+                break;

+            case ']':

+                throw new IllegalStateException(String.format("Position %s: unexpected '%s'", here, c));

+            case '[':

+                tokens.add(parseIndex(expr, next(pos)));

+                break;

+            case '.':

+                if (sep)

+                {

+                    throw new IllegalStateException(String.format(

+                        "Position %s: expected property, index/key, or end of expression", here));

+                }

+                sep = true;

+                next(pos);

+                // fall through:

+            default:

+                if (!sep)

+                {

+                    throw new IllegalStateException(String.format(

+                        "Position %s: expected property path separator, index/key, or end of expression", here));

+                }

+                tokens.add(parseProperty(expr, pos));

+            }

+            sep = false;

+        }

+        return tokens.toArray(new String[tokens.size()]);

+    }

+

+    private static ParsePosition next(ParsePosition pos)

+    {

+        pos.setIndex(pos.getIndex() + 1);

+        return pos;

+    }

+

+    private static String parseProperty(CharSequence expr, ParsePosition pos)

+    {

+        int len = expr.length();

+        int start = pos.getIndex();

+        loop: while (pos.getIndex() < len)

+        {

+            switch (expr.charAt(pos.getIndex()))

+            {

+            case '[':

+            case ']':

+            case '.':

+                break loop;

+            default:

+            }

+            next(pos);

+        }

+        if (pos.getIndex() > start)

+        {

+            return expr.subSequence(start, pos.getIndex()).toString();

+        }

+        throw new IllegalStateException(String.format("Position %s: expected property", start));

+    }

+

+    /**

+     * Handles an index/key. If the text contained between [] is surrounded by a

+     * pair of " or ', these will be stripped.

+     * 

+     * @param expr Expression string to tokenize

+     * @param pos current position of parser, will be updated by the method.

+     * @return token found on the position

+     */

+    private static String parseIndex(CharSequence expr, ParsePosition pos)

+    {

+        int len = expr.length();

+        int start = pos.getIndex();

+        if (start < len)

+        {

+            char first = expr.charAt(pos.getIndex());

+            if (first == '"' || first == '\'')

+            {

+                String s = parseQuotedString(expr, pos);

+                if (s != null && expr.charAt(pos.getIndex()) == ']')

+                {

+                    next(pos);

+                    return s;

+                }

+            }

+            // no quoted string; match ] greedily and trim

+            while (pos.getIndex() < len)

+            {

+                int here = pos.getIndex();

+                try

+                {

+                    if (expr.charAt(here) == ']')

+                    {

+                        return expr.subSequence(start, here).toString().trim();

+                    }

+                }

+                finally

+                {

+                    next(pos);

+                }

+            }

+        }

+        throw new IllegalStateException(String.format("Position %s: unparsable index", start));

+    }

+

+    private static String parseQuotedString(CharSequence expr, ParsePosition pos)

+    {

+        int len = expr.length();

+        int start = pos.getIndex();

+        if (start < len)

+        {

+            char quote = expr.charAt(start);

+            next(pos);

+            StringWriter w = new StringWriter();

+            while (pos.getIndex() < len)

+            {

+                int here = pos.getIndex();

+                char c = expr.charAt(here);

+                boolean esc = false;

+                if (c == '\\' && here + 1 < len && expr.charAt(here + 1) == quote)

+                {

+                    esc = true;

+                    here = next(pos).getIndex();

+                }

+                try

+                {

+                    // look for matching quote

+                    if (c == quote && !esc)

+                    {

+                        return w.toString();

+                    }

+                    w.write(Character.toChars(Character.codePointAt(expr, here)));

+                }

+                catch (IOException e)

+                {

+                    throw new RuntimeException(e);

+                }

+                finally

+                {

+                    next(pos);

+                }

+            }

+            // if reached, reset due to no ending quote found

+            pos.setIndex(start);

+        }

+        return null;

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/el/FacesImplicitObjectELResolver.java b/test30/src/main/java/org/apache/myfaces/test/el/FacesImplicitObjectELResolver.java
new file mode 100644
index 0000000..94e7155
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/FacesImplicitObjectELResolver.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.myfaces.test.el;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p><code>ELResolver</code> implementation that accesses implicit objects
+ * in the current request context.  See the JSF 1.2 Specification, section
+ * 5.6.2.1, for requirements implemented by this class.</p>
+ *
+ * @since 1.0.0
+ */
+public class FacesImplicitObjectELResolver extends AbstractELResolver
+{
+
+    /**
+     * <p>The names of all implicit objects recognized by this resolver.</p>
+     */
+    private static final String[] NAMES = { "application", "applicationScope",
+            "cookie", "facesContext", "header", "headerValues", "initParam",
+            "param", "paramValues", "request", "requestScope", "session",
+            "sessionScope", "view" };
+
+    /**
+     * <p>The property types corresponding to the implicit object names.</p>
+     */
+    private static final Class[] TYPES = { Object.class, Map.class, Map.class,
+            FacesContext.class, Map.class, Map.class, Map.class, Map.class,
+            Map.class, Object.class, Map.class, Object.class, Map.class,
+            UIViewRoot.class };
+
+    /**
+     * <p>The settable value types corresponding to the implicit
+     * object names.</p>
+     */
+    private static final Class[] VALUES = { null, Object.class, null, null,
+            null, null, null, null, null, null, Object.class, null,
+            Object.class, null };
+
+    /**
+     * <p>Return the most general type this resolver accepts for the
+     * <code>property</code> argument.</p>
+     */
+    public Class getCommonPropertyType(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        else
+        {
+            return String.class;
+        }
+
+    }
+
+    /**
+     * <p>Return an <code>Iterator</code> over the attributes that this
+     * resolver knows how to deal with.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     */
+    public Iterator getFeatureDescriptors(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+
+        // Create the variables we will need
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        List descriptors = new ArrayList();
+
+        // Add feature descriptors for each implicit object
+        for (int i = 0; i < NAMES.length; i++)
+        {
+            descriptors.add(descriptor(NAMES[i], NAMES[i], NAMES[i], false,
+                    false, true, TYPES[i], true));
+        }
+
+        // Return the accumulated descriptors
+        return descriptors.iterator();
+
+    }
+
+    /**
+     * <p>Return the Java type of the specified property.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Class getType(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+        String name = property.toString();
+        for (int i = 0; i < NAMES.length; i++)
+        {
+            if (name.equals(NAMES[i]))
+            {
+                context.setPropertyResolved(true);
+                return VALUES[i];
+            }
+        }
+        return null;
+
+    }
+
+    /**
+     * <p>Return an existing scoped object for the specified name (if any);
+     * otherwise, return <code>null</code>.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Object getValue(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ExternalContext econtext = fcontext.getExternalContext();
+        String name = property.toString();
+
+        if (name.equals("application"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getContext();
+        }
+        else if (name.equals("applicationScope"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getApplicationMap();
+        }
+        else if (name.equals("cookie"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestCookieMap();
+        }
+        else if (name.equals("facesContext"))
+        {
+            context.setPropertyResolved(true);
+            return fcontext;
+        }
+        else if (name.equals("header"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestHeaderMap();
+        }
+        else if (name.equals("headerValues"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestHeaderValuesMap();
+        }
+        else if (name.equals("initParam"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getInitParameterMap();
+        }
+        else if (name.equals("param"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestParameterMap();
+        }
+        else if (name.equals("paramValues"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestParameterValuesMap();
+        }
+        else if (name.equals("request"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequest();
+        }
+        else if (name.equals("requestScope"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getRequestMap();
+        }
+        else if (name.equals("session"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getSession(true);
+        }
+        else if (name.equals("sessionScope"))
+        {
+            context.setPropertyResolved(true);
+            return econtext.getSessionMap();
+        }
+        else if (name.equals("view"))
+        {
+            context.setPropertyResolved(true);
+            return fcontext.getViewRoot();
+        }
+
+        return null;
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the specified property is read only.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return false;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+        String name = property.toString();
+        for (int i = 0; i < NAMES.length; i++)
+        {
+            if (name.equals(NAMES[i]))
+            {
+                context.setPropertyResolved(true);
+                return true;
+            }
+        }
+        return false;
+
+    }
+
+    /**
+     * <p>Set the value of a scoped object for the specified name.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     * @param value New value to be set
+     */
+    public void setValue(ELContext context, Object base, Object property,
+            Object value)
+    {
+
+        if (base != null)
+        {
+            return;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        String name = property.toString();
+        for (int i = 0; i < NAMES.length; i++)
+        {
+            if (name.equals(NAMES[i]))
+            {
+                context.setPropertyResolved(true);
+                throw new PropertyNotWritableException(name);
+            }
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/FacesResourceBundleELResolver.java b/test30/src/main/java/org/apache/myfaces/test/el/FacesResourceBundleELResolver.java
new file mode 100644
index 0000000..e7adb25
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/FacesResourceBundleELResolver.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.el;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Map.Entry;
+
+import javax.el.ELContext;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.faces.context.FacesContext;
+
+import org.apache.myfaces.test.mock.MockApplication12;
+
+/**
+ * <p><code>ELResolver</code> implementation that accesses resource bundles
+ * in the current application.  See the JSF 1.2 Specification, section
+ * 5.6.1.3, for requirements implemented by this class.</p>
+ *
+ * @since 1.0.0
+ */
+public class FacesResourceBundleELResolver extends AbstractELResolver
+{
+
+    /**
+     * <p>Return the most general type this resolver accepts for the
+     * <code>property</code> argument.</p>
+     */
+    public Class getCommonPropertyType(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        else
+        {
+            return String.class;
+        }
+
+    }
+
+    /**
+     * <p>Return an <code>Iterator</code> over the attributes that this
+     * resolver knows how to deal with.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     */
+    public Iterator getFeatureDescriptors(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+
+        // Create the variables we will need
+        List descriptors = new ArrayList();
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        MockApplication12 application = (MockApplication12) fcontext
+                .getApplication();
+        String key = null;
+        Object value = null;
+
+        // Create a feature descriptor for each configured resource bundle
+        Iterator entries = application.getResourceBundles().entrySet()
+                .iterator();
+        while (entries.hasNext())
+        {
+            Entry entry = (Entry) entries.next();
+            key = (String) entry.getKey();
+            value = entry.getValue();
+            descriptors.add(descriptor(key, key, "Resource Bundle " + key,
+                    false, false, true, ResourceBundle.class, true));
+        }
+
+        // Return the accumulated descriptors
+        return descriptors.iterator();
+
+    }
+
+    /**
+     * <p>Return the Java type of the specified property.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Class getType(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ResourceBundle bundle = fcontext.getApplication().getResourceBundle(
+                fcontext, property.toString());
+        if (bundle != null)
+        {
+            context.setPropertyResolved(true);
+            return ResourceBundle.class;
+        }
+        return null;
+
+    }
+
+    /**
+     * <p>Return a resource bundle for the specified name (if any);
+     * otherwise, return <code>null</code>.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Object getValue(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ResourceBundle bundle = fcontext.getApplication().getResourceBundle(
+                fcontext, property.toString());
+        if (bundle != null)
+        {
+            context.setPropertyResolved(true);
+            return bundle;
+        }
+        return null;
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the specified property is read only.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return false;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ResourceBundle bundle = fcontext.getApplication().getResourceBundle(
+                fcontext, property.toString());
+        if (bundle != null)
+        {
+            context.setPropertyResolved(true);
+            return true;
+        }
+        return false;
+
+    }
+
+    /**
+     * <p>Set the value of a scoped object for the specified name.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     * @param value New value to be set
+     */
+    public void setValue(ELContext context, Object base, Object property,
+            Object value)
+    {
+
+        if (base != null)
+        {
+            return;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ResourceBundle bundle = fcontext.getApplication().getResourceBundle(
+                fcontext, property.toString());
+        if (bundle != null)
+        {
+            context.setPropertyResolved(true);
+            throw new PropertyNotWritableException(property.toString());
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/FacesScopedAttributeELResolver.java b/test30/src/main/java/org/apache/myfaces/test/el/FacesScopedAttributeELResolver.java
new file mode 100644
index 0000000..bd2e6df
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/FacesScopedAttributeELResolver.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.myfaces.test.el;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.el.ELContext;
+import javax.el.PropertyNotFoundException;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p><code>ELResolver</code> implementation that accesses scoped variables
+ * in the current request context.  See the JSF 1.2 Specification, section
+ * 5.6.2.7, for requirements implemented by this class.</p>
+ *
+ * @since 1.0.0
+ */
+public class FacesScopedAttributeELResolver extends AbstractELResolver
+{
+
+    /**
+     * <p>Return the most general type this resolver accepts for the
+     * <code>property</code> argument.</p>
+     */
+    public Class getCommonPropertyType(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        else
+        {
+            return String.class;
+        }
+
+    }
+
+    /**
+     * <p>Return an <code>Iterator</code> over the attributes that this
+     * resolver knows how to deal with.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     */
+    public Iterator getFeatureDescriptors(ELContext context, Object base)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+
+        // Create the variables we will need
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ExternalContext econtext = fcontext.getExternalContext();
+        List descriptors = new ArrayList();
+        Map map = null;
+        Set set = null;
+        Iterator items = null;
+        String key = null;
+        Object value = null;
+
+        // Add feature descriptors for request scoped attributes
+        set = econtext.getRequestMap().entrySet();
+        items = set.iterator();
+        while (items.hasNext())
+        {
+            Entry item = (Entry) items.next();
+            key = (String) item.getKey();
+            value = item.getValue();
+            descriptors.add(descriptor(key, key, "Request Scope Attribute "
+                    + key, false, false, true, value.getClass(), true));
+        }
+
+        // Add feature descriptors for session scoped attributes
+        set = econtext.getSessionMap().entrySet();
+        items = set.iterator();
+        while (items.hasNext())
+        {
+            Entry item = (Entry) items.next();
+            key = (String) item.getKey();
+            value = item.getValue();
+            descriptors.add(descriptor(key, key, "Session Scope Attribute "
+                    + key, false, false, true, value.getClass(), true));
+        }
+
+        // Add feature descriptors for application scoped attributes
+        set = econtext.getApplicationMap().entrySet();
+        items = set.iterator();
+        while (items.hasNext())
+        {
+            Entry item = (Entry) items.next();
+            key = (String) item.getKey();
+            value = item.getValue();
+            descriptors.add(descriptor(key, key, "Application Scope Attribute "
+                    + key, false, false, true, value.getClass(), true));
+        }
+
+        // Return the accumulated descriptors
+        return descriptors.iterator();
+
+    }
+
+    /**
+     * <p>Return the Java type of the specified property.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Class getType(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+        context.setPropertyResolved(true);
+        return Object.class;
+
+    }
+
+    /**
+     * <p>Return an existing scoped object for the specified name (if any);
+     * otherwise, return <code>null</code>.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public Object getValue(ELContext context, Object base, Object property)
+    {
+
+        if (base != null)
+        {
+            return null;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ExternalContext econtext = fcontext.getExternalContext();
+        Object value = null;
+        value = econtext.getRequestMap().get(property);
+        if (value != null)
+        {
+            context.setPropertyResolved(true);
+            return value;
+        }
+        value = econtext.getSessionMap().get(property);
+        if (value != null)
+        {
+            context.setPropertyResolved(true);
+            return value;
+        }
+        value = econtext.getApplicationMap().get(property);
+        if (value != null)
+        {
+            context.setPropertyResolved(true);
+            return value;
+        }
+
+        return null;
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the specified property is read only.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     */
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+    {
+
+        if (base == null)
+        {
+            context.setPropertyResolved(true);
+            return false;
+        }
+        return false;
+
+    }
+
+    /**
+     * <p>Set the value of a scoped object for the specified name.</p>
+     *
+     * @param context <code>ELContext</code> for evaluating this value
+     * @param base Base object against which this evaluation occurs
+     *  (must be null because we are evaluating a top level variable)
+     * @param property Property name to be accessed
+     * @param value New value to be set
+     */
+    public void setValue(ELContext context, Object base, Object property,
+            Object value)
+    {
+
+        if (base != null)
+        {
+            return;
+        }
+        if (property == null)
+        {
+            throw new PropertyNotFoundException("No property specified");
+        }
+
+        context.setPropertyResolved(true);
+        String key = property.toString();
+        Object result = null;
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ExternalContext econtext = fcontext.getExternalContext();
+
+        if (econtext.getRequestMap().containsKey(property))
+        {
+            econtext.getRequestMap().put(key, value);
+        }
+        else if (econtext.getSessionMap().containsKey(property))
+        {
+            econtext.getSessionMap().put(key, value);
+        }
+        else if (econtext.getApplicationMap().containsKey(property))
+        {
+            econtext.getApplicationMap().put(key, value);
+        }
+        else
+        {
+            econtext.getRequestMap().put(key, value);
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/FacesVariableResolverChainWrapper.java b/test30/src/main/java/org/apache/myfaces/test/el/FacesVariableResolverChainWrapper.java
new file mode 100644
index 0000000..e3b89de
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/FacesVariableResolverChainWrapper.java
@@ -0,0 +1,143 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.el;

+

+import java.util.Iterator;

+

+import javax.el.ELContext;

+import javax.el.PropertyNotFoundException;

+import javax.faces.FacesException;

+

+/**

+ * <p><code>ELResolver</code> implementation that wraps the legacy (JSF 1.1)

+ * <code>VariableResolver</code> chain.  See the JSF 1.2 Specification, section

+ * 5.6.1.5, for requirements implemented by this class.</p>

+ *

+ * @since 1.0.0

+ */

+public class FacesVariableResolverChainWrapper extends AbstractELResolver

+{

+

+    /**

+     * <p>Return the most general type this resolver accepts for the

+     * <code>property</code> argument.</p>

+     */

+    public Class getCommonPropertyType(ELContext context, Object base)

+    {

+

+        if (base != null)

+        {

+            return null;

+        }

+        else

+        {

+            return String.class;

+        }

+

+    }

+

+    /**

+     * <p>Return an <code>Iterator</code> over the attributes that this

+     * resolver knows how to deal with.</p>

+     *

+     * @param context <code>ELContext</code> for evaluating this value

+     * @param base Base object against which this evaluation occurs

+     */

+    public Iterator getFeatureDescriptors(ELContext context, Object base)

+    {

+

+        return null;

+

+    }

+

+    /**

+     * <p>Return the Java type of the specified property.</p>

+     *

+     * @param context <code>ELContext</code> for evaluating this value

+     * @param base Base object against which this evaluation occurs

+     *  (must be null because we are evaluating a top level variable)

+     * @param property Property name to be accessed

+     */

+    public Class getType(ELContext context, Object base, Object property)

+    {

+

+        if ((base == null) && (property == null))

+        {

+            throw new PropertyNotFoundException("No property specified");

+        }

+        return null;

+

+    }

+

+    /**

+     * <p>Evaluate with the legacy variable resolver chain and return

+     * the value.</p>

+     *

+     * @param context <code>ELContext</code> for evaluating this value

+     * @param base Base object against which this evaluation occurs

+     *  (must be null because we are evaluating a top level variable)

+     * @param property Property name to be accessed

+     */

+    public Object getValue(ELContext context, Object base, Object property)

+    {

+        throw new FacesException("not supported anymore");

+

+    }

+

+    /**

+     * <p>Return <code>true</code> if the specified property is read only.</p>

+     *

+     * @param context <code>ELContext</code> for evaluating this value

+     * @param base Base object against which this evaluation occurs

+     *  (must be null because we are evaluating a top level variable)

+     * @param property Property name to be accessed

+     */

+    public boolean isReadOnly(ELContext context, Object base, Object property)

+    {

+

+        if ((base == null) && (property == null))

+        {

+            throw new PropertyNotFoundException("No property specified");

+        }

+        return false;

+

+    }

+

+    /**

+     * <p>Set the value of a scoped object for the specified name.</p>

+     *

+     * @param context <code>ELContext</code> for evaluating this value

+     * @param base Base object against which this evaluation occurs

+     *  (must be null because we are evaluating a top level variable)

+     * @param property Property name to be accessed

+     * @param value New value to be set

+     */

+    public void setValue(ELContext context, Object base, Object property,

+            Object value)

+    {

+

+        if ((base == null) && (property == null))

+        {

+            throw new PropertyNotFoundException("No property specified");

+        }

+

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockCompositeValueExpression.java b/test30/src/main/java/org/apache/myfaces/test/el/MockCompositeValueExpression.java
new file mode 100644
index 0000000..ce77bab
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockCompositeValueExpression.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.myfaces.test.el;

+

+import java.util.ArrayList;

+import java.util.List;

+

+import javax.el.ELContext;

+import javax.el.ValueExpression;

+

+/**

+ * A value expression implementation that is capable of handling composite expressions.

+ * It handles composites expressions but creating a list of 'simple' expressions which 

+ * are 'pure', only literal text or only references like #{}

+ * 

+ * @author Rudy De Busscher

+ * @since 1.0.0

+ */

+public class MockCompositeValueExpression extends MockValueExpression

+{

+

+    private static final long serialVersionUID = 2645070462654392076L;

+

+    private List<ValueExpression> valueExpressionChain;

+

+    public MockCompositeValueExpression(String expression, Class expectedType)

+    {

+

+        super("#{}", expectedType);

+        buildExpressionChain(expression, expectedType);

+

+    }

+

+    private void buildExpressionChain(String expression, Class expectedType)

+    {

+        valueExpressionChain = new ArrayList<ValueExpression>();

+        StringBuilder parser = new StringBuilder(expression);

+        int pos = getStartPositionOfReference(parser);

+        while (pos > -1 || parser.length() > 0)

+        {

+            // We have a constant first

+            if (pos > 0)

+            {

+                valueExpressionChain.add(new MockValueExpression(parser

+                        .substring(0, pos), expectedType));

+                parser.delete(0, pos);

+            }

+            // We have an el, maybe literal at the end

+            if (pos == 0)

+            {

+                int posBracket = parser.indexOf("}");

+                valueExpressionChain.add(new MockValueExpression(parser

+                        .substring(0, posBracket + 1), expectedType));

+

+                parser.delete(0, posBracket + 1);

+            }

+            // Only literal

+            if (pos == -1)

+            {

+                valueExpressionChain.add(new MockValueExpression(parser

+                        .toString(), expectedType));

+

+                parser.setLength(0);

+            }

+            pos = getStartPositionOfReference(parser);

+        }

+    }

+

+    @Override

+    public Class getType(ELContext context)

+    {

+        switch (valueExpressionChain.size())

+        {

+        case 0:

+            return null;

+        case 1:

+            return valueExpressionChain.get(0).getType(context);

+        default:

+            return String.class;

+        }

+    }

+

+    @Override

+    public Object getValue(ELContext context)

+    {

+        if (valueExpressionChain.size() > 1)

+        {

+            // Well only composite strings are supported.

+

+            StringBuilder result = new StringBuilder();

+            for (ValueExpression valueExpression : valueExpressionChain)

+            {

+                result.append(valueExpression.getValue(context));

+            }

+            return result.toString();

+        }

+        else

+        {

+            if (valueExpressionChain.size() == 1)

+            {

+                return valueExpressionChain.get(0).getValue(context);

+            }

+            else

+            {

+                return null;

+            }

+        }

+    }

+

+    @Override

+    public void setValue(ELContext context, Object value)

+    {

+        if (!isReadOnly(context))

+        {

+            valueExpressionChain.get(0).setValue(context, value);

+        }

+        else

+        {

+            throw new IllegalArgumentException(

+                    "We can only set value on NON composite expressions like #{foo}");

+        }

+    }

+

+    @Override

+    public String getExpressionString()

+    {

+        StringBuilder result = new StringBuilder();

+        for (ValueExpression valueExpression : valueExpressionChain)

+        {

+            result.append(valueExpression.getExpressionString());

+        }

+        return result.toString();

+    }

+

+    @Override

+    public boolean isReadOnly(ELContext context)

+    {

+        return valueExpressionChain.size() > 1;

+    }

+

+    public static int getStartPositionOfReference(StringBuilder expressionPart)

+    {

+        int result;

+        int pos1 = expressionPart.indexOf("#{");

+        int pos2 = expressionPart.indexOf("${");

+

+        if (pos1 == -1)

+        {

+            result = pos1;

+        }

+        else if (pos2 == -1)

+        {

+            result = pos1;

+        }

+        else

+        {

+            result = Math.min(pos1, pos2);

+        }

+        return result;

+

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockELContext.java b/test30/src/main/java/org/apache/myfaces/test/el/MockELContext.java
new file mode 100644
index 0000000..2b2130f
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockELContext.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.myfaces.test.el;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>ELContext</code>.</p>
+ *
+ * @since 1.0.0
+ */
+
+public class MockELContext extends ELContext
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /** Creates a new instance of MockELContext */
+    public MockELContext()
+    {
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private Map contexts = new HashMap();
+    private FunctionMapper functionMapper = new MockFunctionMapper();
+    private Locale locale = Locale.getDefault();
+    private boolean propertyResolved;
+    private VariableMapper variableMapper = new MockVariableMapper();
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------- ELContext Methods
+
+    /** {@inheritDoc} */
+    public Object getContext(Class key)
+    {
+        if (key == null)
+        {
+            throw new NullPointerException();
+        }
+        return contexts.get(key);
+    }
+
+    /** {@inheritDoc} */
+    public ELResolver getELResolver()
+    {
+        FacesContext context = FacesContext.getCurrentInstance();
+        return context.getApplication().getELResolver();
+    }
+
+    /** {@inheritDoc} */
+    public FunctionMapper getFunctionMapper()
+    {
+        return this.functionMapper;
+    }
+
+    /** {@inheritDoc} */
+    public Locale getLocale()
+    {
+        return this.locale;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isPropertyResolved()
+    {
+        return this.propertyResolved;
+    }
+
+    /** {@inheritDoc} */
+    public void putContext(Class key, Object value)
+    {
+        if ((key == null) || (value == null))
+        {
+            throw new NullPointerException();
+        }
+        contexts.put(key, value);
+    }
+
+    /** {@inheritDoc} */
+    public void setPropertyResolved(boolean propertyResolved)
+    {
+        this.propertyResolved = propertyResolved;
+    }
+
+    /** {@inheritDoc} */
+    public VariableMapper getVariableMapper()
+    {
+        return this.variableMapper;
+    }
+
+    /** {@inheritDoc} */
+    public void setLocale(Locale locale)
+    {
+        this.locale = locale;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockExpressionFactory.java b/test30/src/main/java/org/apache/myfaces/test/el/MockExpressionFactory.java
new file mode 100644
index 0000000..34e5c74
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockExpressionFactory.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.myfaces.test.el;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+
+/**
+ * <p>Mock implementation of <code>ExpressionFactory</code>.</p>
+ *
+ * @since 1.0.0
+ */
+public class MockExpressionFactory extends ExpressionFactory
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /** Creates a new instance of MockExpressionFactory */
+    public MockExpressionFactory()
+    {
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>Literal numeric value for zero.</p>
+     */
+    private static final Integer ZERO = new Integer(0);
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ----------------------------------------------- ExpressionFactory Methods
+
+    /** {@inheritDoc} */
+    public Object coerceToType(Object object, Class targetType)
+    {
+
+        // Check for no conversion necessary
+        if ((targetType == null) || Object.class.equals(targetType))
+        {
+            return object;
+        }
+
+        // Coerce to String if appropriate
+        if (String.class.equals(targetType))
+        {
+            if (object == null)
+            {
+                return "";
+            }
+            else if (object instanceof String)
+            {
+                return (String) object;
+            }
+            else
+            {
+                return object.toString();
+            }
+        }
+
+        // Coerce to Number (or a subclass of Number) if appropriate
+        if (isNumeric(targetType))
+        {
+            if (object == null)
+            {
+                return coerce(ZERO, targetType);
+            }
+            else if ("".equals(object))
+            {
+                return coerce(ZERO, targetType);
+            }
+            else if (object instanceof String)
+            {
+                return coerce((String) object, targetType);
+            }
+            else if (isNumeric(object.getClass()))
+            {
+                return coerce((Number) object, targetType);
+            }
+            throw new IllegalArgumentException("Cannot convert " + object
+                    + " to Number");
+        }
+
+        // Coerce to Boolean if appropriate
+        if (Boolean.class.equals(targetType) || (Boolean.TYPE == targetType))
+        {
+            if (object == null)
+            {
+                return Boolean.FALSE;
+            }
+            else if ("".equals(object))
+            {
+                return Boolean.FALSE;
+            }
+            else if ((object instanceof Boolean)
+                    || (object.getClass() == Boolean.TYPE))
+            {
+                return (Boolean) object;
+            }
+            else if (object instanceof String)
+            {
+                return Boolean.valueOf((String) object);
+            }
+            throw new IllegalArgumentException("Cannot convert " + object
+                    + " to Boolean");
+        }
+
+        // Coerce to Character if appropriate
+        if (Character.class.equals(targetType)
+                || (Character.TYPE == targetType))
+        {
+            if (object == null)
+            {
+                return new Character((char) 0);
+            }
+            else if ("".equals(object))
+            {
+                return new Character((char) 0);
+            }
+            else if (object instanceof String)
+            {
+                return new Character(((String) object).charAt(0));
+            }
+            else if (isNumeric(object.getClass()))
+            {
+                return new Character((char) ((Number) object).shortValue());
+            }
+            else if ((object instanceof Character)
+                    || (object.getClass() == Character.TYPE))
+            {
+                return (Character) object;
+            }
+            throw new IllegalArgumentException("Cannot convert " + object
+                    + " to Character");
+        }
+        
+        if (targetType.isEnum())
+        {
+            if (object == null || "".equals(object))
+            {
+                return null;
+            }
+            if (targetType.isAssignableFrom(object.getClass()))
+            {
+                return (Enum) object;
+            }
+            
+            if (!(object instanceof String))
+            {
+                throw new IllegalArgumentException("Cannot convert " + object + " to Enum");
+            }
+
+            Enum<?> result;
+            try
+            {
+                 result = Enum.valueOf(targetType, (String) object);
+                 return result;
+            }
+            catch (IllegalArgumentException iae)
+            {
+                throw new IllegalArgumentException("Cannot convert " + object + " to Enum");
+            }
+        }
+
+        // Is the specified value type-compatible already?
+        if ((object != null) && targetType.isAssignableFrom(object.getClass()))
+        {
+            return object;
+        }
+
+        // new to spec
+        if (object == null)
+        {
+            return null;
+        }
+
+        // We do not know how to perform this conversion
+        throw new IllegalArgumentException("Cannot convert " + object + " to "
+                + targetType.getName());
+
+    }
+
+    /** {@inheritDoc} */
+    public MethodExpression createMethodExpression(ELContext context,
+            String expression, Class expectedType, Class[] signature)
+    {
+
+        return new MockMethodExpression(expression, signature, expectedType);
+
+    }
+
+    /** {@inheritDoc} */
+    public ValueExpression createValueExpression(ELContext context,
+            String expression, Class expectedType)
+    {
+
+        return new MockCompositeValueExpression(expression, expectedType);
+
+    }
+
+    /** {@inheritDoc} */
+    public ValueExpression createValueExpression(Object instance,
+            Class expectedType)
+    {
+
+        return new MockVariableValueExpression(instance, expectedType);
+
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>Coerce the specified value to the specified Number subclass.</p>
+     *
+     * @param value Value to be coerced
+     * @param type Destination type
+     */
+    private Number coerce(Number value, Class type)
+    {
+
+        if ((type == Byte.TYPE) || (type == Byte.class))
+        {
+            return new Byte(value.byteValue());
+        }
+        else if ((type == Double.TYPE) || (type == Double.class))
+        {
+            return new Double(value.doubleValue());
+        }
+        else if ((type == Float.TYPE) || (type == Float.class))
+        {
+            return new Float(value.floatValue());
+        }
+        else if ((type == Integer.TYPE) || (type == Integer.class))
+        {
+            return new Integer(value.intValue());
+        }
+        else if ((type == Long.TYPE) || (type == Long.class))
+        {
+            return new Long(value.longValue());
+        }
+        else if ((type == Short.TYPE) || (type == Short.class))
+        {
+            return new Short(value.shortValue());
+        }
+        else if (type == BigDecimal.class)
+        {
+            if (value instanceof BigDecimal)
+            {
+                return (BigDecimal) value;
+            }
+            else if (value instanceof BigInteger)
+            {
+                return new BigDecimal((BigInteger) value);
+            }
+            else
+            {
+                return new BigDecimal(((Number) value).doubleValue());
+            }
+        }
+        else if (type == BigInteger.class)
+        {
+            if (value instanceof BigInteger)
+            {
+                return (BigInteger) value;
+            }
+            else if (value instanceof BigDecimal)
+            {
+                return ((BigDecimal) value).toBigInteger();
+            }
+            else
+            {
+                return BigInteger.valueOf(((Number) value).longValue());
+            }
+        }
+        throw new IllegalArgumentException("Cannot convert " + value + " to "
+                + type.getName());
+
+    }
+
+    /**
+     * <p>Coerce the specified value to the specified Number subclass.</p>
+     *
+     * @param value Value to be coerced
+     * @param type Destination type
+     */
+    private Number coerce(String value, Class type)
+    {
+
+        if ((type == Byte.TYPE) || (type == Byte.class))
+        {
+            return Byte.valueOf(value);
+        }
+        else if ((type == Double.TYPE) || (type == Double.class))
+        {
+            return Double.valueOf(value);
+        }
+        else if ((type == Float.TYPE) || (type == Float.class))
+        {
+            return Float.valueOf(value);
+        }
+        else if ((type == Integer.TYPE) || (type == Integer.class))
+        {
+            return Integer.valueOf(value);
+        }
+        else if ((type == Long.TYPE) || (type == Long.class))
+        {
+            return Long.valueOf(value);
+        }
+        else if ((type == Short.TYPE) || (type == Short.class))
+        {
+            return Short.valueOf(value);
+        }
+        else if (type == BigDecimal.class)
+        {
+            return new BigDecimal(value);
+        }
+        else if (type == BigInteger.class)
+        {
+            return new BigInteger(value);
+        }
+        throw new IllegalArgumentException("Cannot convert " + value + " to "
+                + type.getName());
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the specified type is numeric.</p>
+     *
+     * @param type Type to check
+     */
+    private boolean isNumeric(Class type)
+    {
+
+        return (type == Byte.TYPE) || (type == Byte.class)
+                || (type == Double.TYPE) || (type == Double.class)
+                || (type == Float.TYPE) || (type == Float.class)
+                || (type == Integer.TYPE) || (type == Integer.class)
+                || (type == Long.TYPE) || (type == Long.class)
+                || (type == Short.TYPE) || (type == Short.class)
+                || (type == BigDecimal.class) || (type == BigInteger.class);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockFunctionMapper.java b/test30/src/main/java/org/apache/myfaces/test/el/MockFunctionMapper.java
new file mode 100644
index 0000000..4d7d15d
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockFunctionMapper.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.myfaces.test.el;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import javax.el.FunctionMapper;
+
+/**
+ * <p>Mock implementation of <code>FunctionMapper</code>.</p>
+ *
+ * @since 1.0.0
+ */
+
+public class MockFunctionMapper extends FunctionMapper
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /** Creates a new instance of MockFunctionMapper */
+    public MockFunctionMapper()
+    {
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>Map of <code>Method</code> descriptors for static methods, keyed by
+     * a string composed of the prefix (or "" if none), a ":", and the local name.</p>
+     */
+    private Map functions = new HashMap();
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Store a mapping of the specified prefix and localName to the
+     * specified method, which must be static.</p>
+     */
+    public void mapFunction(String prefix, String localName, Method method)
+    {
+
+        functions.put(prefix + ":" + localName, method);
+
+    }
+
+    // -------------------------------------------------- FunctionMapper Methods
+
+    /** {@inheritDoc} */
+    public Method resolveFunction(String prefix, String localName)
+    {
+
+        return (Method) functions.get(prefix + ":" + localName);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockMethodExpression.java b/test30/src/main/java/org/apache/myfaces/test/el/MockMethodExpression.java
new file mode 100644
index 0000000..04b065a
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockMethodExpression.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.myfaces.test.el;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+import javax.el.MethodNotFoundException;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>MethodExpression</code>.</p>
+ * 
+ * @since 1.0.0
+ */
+public class MockMethodExpression extends MethodExpression
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = 5694105394290316715L;
+
+    /**
+     * <p>Construct a new expression for the specified expression string.</p>
+     *
+     * @param expression Expression string to be evaluated
+     * @param signature Parameter signature of the method to be called
+     * @param expectedType Expected type of the result
+     */
+    public MockMethodExpression(String expression, Class[] signature,
+            Class expectedType)
+    {
+
+        if (expression == null)
+        {
+            throw new NullPointerException("Expression string cannot be null");
+        }
+        this.expression = expression;
+        this.signature = signature;
+        this.expectedType = expectedType;
+        parse();
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The parsed elements of this expression.</p>
+     */
+    private String[] elements = null;
+
+    /**
+     * <p>The expected result type for <code>getValue()</code> calls.</p>
+     */
+    private Class expectedType = null;
+
+    /**
+     * <p>The original expression string used to create this expression.</p>
+     */
+    private String expression = null;
+
+    /**
+     * <p>The method signature of the method to be called.</p>
+     */
+    private Class[] signature = null;
+
+    // ------------------------------------------------------ Expression Methods
+
+    /**
+     * <p>Return <code>true</code> if this expression is equal to the
+     * specified expression.</p>
+     *
+     * @param obj Object to be compared
+     */
+    public boolean equals(Object obj)
+    {
+
+        if ((obj != null) & (obj instanceof MethodExpression))
+        {
+            return expression.equals(((MethodExpression) obj)
+                    .getExpressionString());
+        }
+        else
+        {
+            return false;
+        }
+
+    }
+
+    /**
+     * <p>Return the original String used to create this expression,
+     * unmodified.</p>
+     */
+    public String getExpressionString()
+    {
+
+        return this.expression;
+
+    }
+
+    /**
+     * <p>Return the hash code for this expression.</p>
+     */
+    public int hashCode()
+    {
+
+        return this.expression.hashCode();
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the expression string for this expression
+     * contains only literal text.</p>
+     */
+    public boolean isLiteralText()
+    {
+
+        return (expression.indexOf("${") < 0) && (expression.indexOf("#{") < 0);
+
+    }
+
+    // ------------------------------------------------ MethodExpression Methods
+
+    /**
+     * <p>Evaluate the expression relative to the specified context,
+     * and return information about the actual implementation method.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public MethodInfo getMethodInfo(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        return new MethodInfo(elements[elements.length - 1], expectedType,
+                signature);
+
+    }
+
+    /**
+     * <p>Evaluate the expression relative to the specified ocntext,
+     * and return the result after coercion to the expected result type.</p>
+     *
+     * @param context ELContext for this evaluation
+     * @param params Parameters for this method call
+     */
+    public Object invoke(ELContext context, Object[] params)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        if (isLiteralText())
+        {
+            return expression;
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        ELResolver resolver = fcontext.getApplication().getELResolver();
+        Object base = null;
+        for (int i = 0; i < elements.length - 1; i++)
+        {
+            base = resolver.getValue(context, base, elements[i]);
+        }
+
+        try
+        {
+            Method method = base.getClass().getMethod(
+                    elements[elements.length - 1], signature);
+            Object result = method.invoke(base, params);
+            return fcontext.getApplication().getExpressionFactory()
+                    .coerceToType(result, expectedType);
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new MethodNotFoundException(e);
+        }
+        catch (Exception e)
+        {
+            throw new ELException(e);
+        }
+
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>Parse the expression string into its constituent elemetns.</p>
+     */
+    private void parse()
+    {
+
+        if (isLiteralText())
+        {
+            elements = new String[0];
+            return;
+        }
+
+        if (expression.startsWith("${") || expression.startsWith("#{"))
+        {
+            if (expression.endsWith("}"))
+            {
+                String temp = expression.substring(2, expression.length() - 1)
+                        .replaceAll(" ", "");
+                List names = new ArrayList();
+                while (temp.length() > 0)
+                {
+                    int period = temp.indexOf(".");
+                    if (period >= 0)
+                    {
+                        names.add(temp.substring(0, period));
+                        temp = temp.substring(period + 1);
+                    }
+                    else
+                    {
+                        names.add(temp);
+                        temp = "";
+                    }
+                }
+                elements = (String[]) names.toArray(new String[names.size()]);
+            }
+            else
+            {
+                throw new IllegalArgumentException(expression);
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException(expression);
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockValueExpression.java b/test30/src/main/java/org/apache/myfaces/test/el/MockValueExpression.java
new file mode 100644
index 0000000..8489ea7
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockValueExpression.java
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.el;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.ValueExpression;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>ValueExpression</code>.</p>
+ *
+ * <p>This implementation supports a limited subset of overall expression functionality:</p>
+ * <ul>
+ * <li>A literal string that contains no expression delimiters.</li>
+ * <li>An expression that starts with "#{" or "${", and ends with "}".</li>
+ * </ul>
+ * 
+ * @since 1.0.0
+ */
+public class MockValueExpression extends ValueExpression
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = -8649071428507512623L;
+
+    /**
+     * <p>Construct a new expression for the specified expression string.</p>
+     *
+     * @param expression Expression string to be evaluated
+     * @param expectedType Expected type of the result
+     */
+    public MockValueExpression(String expression, Class expectedType)
+    {
+
+        if (expression == null)
+        {
+            throw new NullPointerException("Expression string cannot be null");
+        }
+        this.expression = expression;
+        this.expectedType = expectedType;
+        parse();
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The parsed elements of this expression.</p>
+     */
+    private String[] elements = null;
+
+    /**
+     * <p>The expected result type for <code>getValue()</code> calls.</p>
+     */
+    private Class expectedType = null;
+
+    /**
+     * <p>The original expression string used to create this expression.</p>
+     */
+    private String expression = null;
+
+    // ------------------------------------------------------ Expression Methods
+
+    /**
+     * <p>Return <code>true</code> if this expression is equal to the
+     * specified expression.</p>
+     *
+     * @param obj Object to be compared
+     */
+    public boolean equals(Object obj)
+    {
+
+        if ((obj != null) & (obj instanceof ValueExpression))
+        {
+            return expression.equals(((ValueExpression) obj)
+                    .getExpressionString());
+        }
+        else
+        {
+            return false;
+        }
+
+    }
+
+    /**
+     * <p>Return the original String used to create this expression,
+     * unmodified.</p>
+     */
+    public String getExpressionString()
+    {
+
+        return this.expression;
+
+    }
+
+    /**
+     * <p>Return the hash code for this expression.</p>
+     */
+    public int hashCode()
+    {
+
+        return this.expression.hashCode();
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the expression string for this expression
+     * contains only literal text.</p>
+     */
+    public boolean isLiteralText()
+    {
+
+        return (expression.indexOf("${") < 0) && (expression.indexOf("#{") < 0);
+
+    }
+
+    // ------------------------------------------------- ValueExpression Methods
+
+    /**
+     * <p>Return the type that the result of this expression will
+     * be coerced to.</p>
+     */
+    public Class getExpectedType()
+    {
+
+        return this.expectedType;
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return the most general type that is acceptable for the
+     * value passed in a <code>setValue()</code> call.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public Class getType(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        Object value = getValue(context);
+        if (value == null)
+        {
+            if (isLiteralText())
+            {
+                return String.class;
+            }
+
+            ELResolver resolver = context.getELResolver();
+            Object base = null;
+            for (int i = 0; i < elements.length - 1; i++)
+            {
+                base = resolver.getValue(context, base, elements[i]);
+            }
+            return resolver.getType(context, base, elements[elements.length - 1]);
+        }
+        else
+        {
+            return value.getClass();
+        }
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return the result.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public Object getValue(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        if (isLiteralText())
+        {
+            return expression;
+        }
+
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+
+        ELResolver resolver = context.getELResolver();
+        Object base = null;
+        for (int i = 0; i < elements.length; i++)
+        {
+            base = resolver.getValue(context, base, elements[i]);
+        }
+        return fcontext.getApplication().getExpressionFactory().coerceToType(
+                base, getExpectedType());
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return <code>true</code> if a call to <code>setValue()</code>
+     * will always fail.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public boolean isReadOnly(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        if (isLiteralText())
+        {
+            return true;
+        }
+
+        ELResolver resolver = context.getELResolver();
+        Object base = null;
+        for (int i = 0; i < elements.length - 1; i++)
+        {
+            base = resolver.getValue(context, base, elements[i]);
+        }
+        return resolver
+                .isReadOnly(context, base, elements[elements.length - 1]);
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and set the result to the specified value.</p>
+     *
+     * @param context ELContext for this evaluation
+     * @param value Value to which the result should be set
+     */
+    public void setValue(ELContext context, Object value)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+
+        ELResolver resolver = context.getELResolver();
+        Object base = null;
+        for (int i = 0; i < elements.length - 1; i++)
+        {
+            base = resolver.getValue(context, base, elements[i]);
+        }
+        resolver.setValue(context, base, elements[elements.length - 1], value);
+
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>Parse the expression string into its constituent elemetns.</p>
+     */
+    private void parse()
+    {
+
+        if (isLiteralText())
+        {
+            elements = new String[0];
+            return;
+        }
+
+        if (expression.startsWith("${") || expression.startsWith("#{"))
+        {
+            if (expression.endsWith("}"))
+            {
+                elements = ExpressionTokenizer.tokenize(expression.substring(2, expression.length() - 1));
+            }
+            else
+            {
+                throw new IllegalArgumentException(expression);
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException(expression);
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockVariableMapper.java b/test30/src/main/java/org/apache/myfaces/test/el/MockVariableMapper.java
new file mode 100644
index 0000000..b19e7bd
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockVariableMapper.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.myfaces.test.el;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+/**
+ * <p>Mock implementation of <code>VariableMapper</code>.</p>
+ *
+ * @since 1.0.0
+ */
+
+public class MockVariableMapper extends VariableMapper
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /** Creates a new instance of MockVariableMapper */
+    public MockVariableMapper()
+    {
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>Map of <code>ValueExpression</code>s, keyed by variable name.</p>
+     */
+    private Map expressions = new HashMap();
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // -------------------------------------------------- FunctionMapper Methods
+
+    /** {@inheritDoc} */
+    public ValueExpression resolveVariable(String variable)
+    {
+
+        return (ValueExpression) expressions.get(variable);
+
+    }
+
+    /** {@inheritDoc} */
+    public ValueExpression setVariable(String variable,
+            ValueExpression expression)
+    {
+
+        ValueExpression original = (ValueExpression) expressions.get(variable);
+        if (expression == null)
+        {
+            expressions.remove(variable);
+        }
+        else
+        {
+            expressions.put(variable, expression);
+        }
+        return original;
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/MockVariableValueExpression.java b/test30/src/main/java/org/apache/myfaces/test/el/MockVariableValueExpression.java
new file mode 100644
index 0000000..734d911
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/MockVariableValueExpression.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.myfaces.test.el;
+
+import javax.el.ELContext;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>ValueExpression</code> that wraps a variable.</p>
+ * 
+ * @since 1.0.0
+ */
+public class MockVariableValueExpression extends ValueExpression
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = 4475919948345298291L;
+
+    /**
+     * <p>Construct a new expression for the specified instance.</p>
+     *
+     * @param instance Variable instance to be wrapped
+     * @param expectedType Expected type of the result
+     */
+    public MockVariableValueExpression(Object instance, Class expectedType)
+    {
+
+        if (instance == null)
+        {
+            throw new NullPointerException("Instance cannot be null");
+        }
+        this.instance = instance;
+        this.expectedType = expectedType;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The expected result type for <code>getValue()</code> calls.</p>
+     */
+    private Class expectedType = null;
+
+    /**
+     * <p>The variable instance being wrapped by this expression.</p>
+     */
+    private Object instance = null;
+
+    // ------------------------------------------------------ Expression Methods
+
+    /**
+     * <p>Return <code>true</code> if this expression is equal to the
+     * specified expression.</p>
+     *
+     * @param obj Object to be compared
+     */
+    public boolean equals(Object obj)
+    {
+
+        if ((obj != null) & (obj instanceof ValueExpression))
+        {
+            return instance.toString().equals(
+                    ((ValueExpression) obj).getExpressionString());
+        }
+        else
+        {
+            return false;
+        }
+
+    }
+
+    /**
+     * <p>Return the original String used to create this expression,
+     * unmodified.</p>
+     */
+    public String getExpressionString()
+    {
+
+        return this.instance.toString();
+
+    }
+
+    /**
+     * <p>Return the hash code for this expression.</p>
+     */
+    public int hashCode()
+    {
+
+        return this.instance.toString().hashCode();
+
+    }
+
+    /**
+     * <p>Return <code>true</code> if the expression string for this expression
+     * contains only literal text.</p>
+     */
+    public boolean isLiteralText()
+    {
+
+        return true;
+
+    }
+
+    // ------------------------------------------------- ValueExpression Methods
+
+    /**
+     * <p>Return the type that the result of this expression will
+     * be coerced to.</p>
+     */
+    public Class getExpectedType()
+    {
+
+        return this.expectedType;
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return the most general type that is acceptable for the
+     * value passed in a <code>setValue()</code> call.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public Class getType(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        return this.instance.getClass();
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return the result.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public Object getValue(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        FacesContext fcontext = (FacesContext) context
+                .getContext(FacesContext.class);
+        return fcontext.getApplication().getExpressionFactory().coerceToType(
+                instance, expectedType);
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and return <code>true</code> if a call to <code>setValue()</code>
+     * will always fail.</p>
+     *
+     * @param context ELContext for this evaluation
+     */
+    public boolean isReadOnly(ELContext context)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+        return true;
+
+    }
+
+    /**
+     * <p>Evaluate this expression relative to the specified context,
+     * and set the result to the specified value.</p>
+     *
+     * @param context ELContext for this evaluation
+     * @param value Value to which the result should be set
+     */
+    public void setValue(ELContext context, Object value)
+    {
+
+        if (context == null)
+        {
+            throw new NullPointerException();
+        }
+
+        throw new PropertyNotWritableException();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/el/ReservedWordsELResolver.java b/test30/src/main/java/org/apache/myfaces/test/el/ReservedWordsELResolver.java
new file mode 100644
index 0000000..2c048b5
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/el/ReservedWordsELResolver.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.myfaces.test.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;

+

+import javax.el.ELContext;

+import javax.el.PropertyNotWritableException;

+

+/**

+ * {@code ELResolver} for reserved words.

+ */

+public class ReservedWordsELResolver extends AbstractELResolver

+{

+

+    private static final Map<String, Object> VALUES;

+

+    static

+    {

+        HashMap<String, Object> values = new HashMap<String, Object>();

+        values.put("true", Boolean.TRUE);

+        values.put("false", Boolean.FALSE);

+        values.put("null", null);

+        VALUES = Collections.unmodifiableMap(values);

+    }

+

+    private List<FeatureDescriptor> featureDescriptors;

+

+    @Override

+    public Object getValue(ELContext context, Object base, Object property)

+    {

+        if (base == null && VALUES.containsKey(property))

+        {

+            context.setPropertyResolved(true);

+            return VALUES.get(property);

+        }

+        return null;

+    }

+

+    @Override

+    public Class<?> getType(ELContext context, Object base, Object property)

+    {

+        if (base == null && VALUES.containsKey(property))

+        {

+            context.setPropertyResolved(true);

+            Object value = VALUES.get(property);

+            return value == null ? null : value.getClass();

+        }

+        return null;

+    }

+

+    @Override

+    public void setValue(ELContext context, Object base, Object property,

+        Object value)

+    {

+        if (base == null && VALUES.containsKey(property))

+        {

+            context.setPropertyResolved(true);

+            throw new PropertyNotWritableException(property.toString());

+        }

+    }

+

+    @Override

+    public boolean isReadOnly(ELContext context, Object base, Object property)

+    {

+        if (base == null && VALUES.containsKey(property))

+        {

+            context.setPropertyResolved(true);

+            return true;

+        }

+        return false;

+    }

+

+    @Override

+    public synchronized Iterator<FeatureDescriptor> getFeatureDescriptors(

+        ELContext context, Object base)

+    {

+        if (featureDescriptors == null)

+        {

+            featureDescriptors = new ArrayList<FeatureDescriptor>();

+            for (Map.Entry<String, Object> e : VALUES.entrySet())

+            {

+                final Class<?> type

+                    = e.getValue() == null ? null : e.getValue().getClass();

+                featureDescriptors.add(descriptor(e.getKey(), e.getKey(),

+                    e.getKey(), false, false, true, type, true));

+            }

+            featureDescriptors

+                = Collections.unmodifiableList(featureDescriptors);

+        }

+        return featureDescriptors.iterator();

+    }

+

+    @Override

+    public Class<?> getCommonPropertyType(ELContext context, Object base)

+    {

+        return base == null ? String.class : null;

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/htmlunit/AbstractHtmlUnitTestCase.java b/test30/src/main/java/org/apache/myfaces/test/htmlunit/AbstractHtmlUnitTestCase.java
new file mode 100644
index 0000000..b1e62b0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/htmlunit/AbstractHtmlUnitTestCase.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.htmlunit;
+
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
+import com.gargoylesoftware.htmlunit.html.HtmlBody;
+import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlHead;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * <p>Abstract base class for system integration tests based on HtmlUnit.
+ * These tests will expect a system property named <code>url</code> to be
+ * present, which will define the URL (including the context path, but
+ * without a trailing slash) of the application to be tested.</p>
+ * 
+ * @since 1.0.0
+ */
+public abstract class AbstractHtmlUnitTestCase extends TestCase
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of the new test case
+     */
+    public AbstractHtmlUnitTestCase(String name)
+    {
+
+        super(name);
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The most recently retrieved page from the server.</p>
+     */
+    protected HtmlPage page = null;
+
+    /**
+     * <p>The calculated URL for the installed "systest" web application.
+     * This value is based on a system property named <code>url</code>,
+     * which must be defined as part of the command line that executes
+     * each test case.</p>
+     */
+    protected URL url = null;
+
+    /**
+     * <p>The web client for this test case.</p>
+     */
+    protected WebClient webClient = null;
+
+    // ------------------------------------------------------ Test Setup Methods
+
+    /**
+     * <p>Set up the instance variables required for this test case.</p>
+     *
+     * @exception Exception if an error occurs
+     */
+    protected void setUp() throws Exception
+    {
+
+        // Calculate the URL for the installed "systest" web application
+        String url = System.getProperty("url");
+        this.url = new URL(url + "/");
+
+        // Initialize HtmlUnit constructs for this test case
+        webClient = new WebClient();
+
+    }
+
+    /**
+     * <p>Return the set of tests included in this test suite.</p>
+     */
+    public static Test suite()
+    {
+
+        return (new TestSuite(AbstractHtmlUnitTestCase.class));
+
+    }
+
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    protected void tearDown() throws Exception
+    {
+
+        page = null;
+        url = null;
+        webClient = null;
+
+    }
+
+    // ------------------------------------------------------- Protected Methods
+
+    /**
+     * <p>Return the body element for the most recently retrieved page.
+     * If there is no such element, return <code>null</code>.</p>
+     *
+     * @exception Exception if an error occurs
+     */
+    protected HtmlBody body() throws Exception
+    {
+
+        Iterator elements = page.getAllHtmlChildElements();
+        while (elements.hasNext())
+        {
+            HtmlElement element = (HtmlElement) elements.next();
+            if (element instanceof HtmlBody)
+            {
+                return ((HtmlBody) element);
+            }
+        }
+        return (null);
+
+    }
+
+    /**
+     * <p>Return the HTML element with the specified <code>id</code> from the
+     * most recently retrieved page.  If there is no such element, return
+     * <code>null</code>.</p>
+     *
+     * @param id Identifier of the requested element.
+     *
+     * @exception Exception if an error occurs
+     */
+    protected HtmlElement element(String id) throws Exception
+    {
+
+        try
+        {
+            return (page.getHtmlElementById(id));
+        }
+        catch (ElementNotFoundException e)
+        {
+            return (null);
+        }
+
+    }
+
+    /**
+     * <p>Return the form with the specified <code>id</code> from the most
+     * recently retrieved page.  If there is no such form, return
+     * <code>null</code>.<p>
+     *
+     * @param id Identifier of the requested form.
+     *
+     * @exception Exception if an error occurs
+     */
+    protected HtmlForm form(String id) throws Exception
+    {
+
+        Iterator forms = page.getForms().iterator();
+        while (forms.hasNext())
+        {
+            HtmlForm form = (HtmlForm) forms.next();
+            if (id.equals(form.getAttributeValue("id")))
+            {
+                return (form);
+            }
+        }
+        return (null);
+
+    }
+
+    /**
+     * <p>Return the head element for the most recently retrieved page.
+     * If there is no such element, return <code>null</code>.</p>
+     *
+     * @exception Exception if an error occurs
+     */
+    protected HtmlHead head() throws Exception
+    {
+
+        Iterator elements = page.getAllHtmlChildElements();
+        while (elements.hasNext())
+        {
+            HtmlElement element = (HtmlElement) elements.next();
+            if (element instanceof HtmlHead)
+            {
+                return ((HtmlHead) element);
+            }
+        }
+        return (null);
+
+    }
+
+    /**
+     * <p>Click the specified hyperlink, and retrieve the subsequent page,
+     * saving a reference so that other utility methods may be used to
+     * retrieve information from it.</p>
+     *
+     * @param anchor Anchor component to click
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected HtmlPage link(HtmlAnchor anchor) throws IOException
+    {
+
+        HtmlPage page = (HtmlPage) anchor.click();
+        this.page = page;
+        return page;
+
+    }
+
+    /**
+     * <p>Return the currently stored page reference.</p>
+     */
+    protected HtmlPage page()
+    {
+
+        return this.page;
+
+    }
+
+    /**
+     * <p>Retrieve and return the page at the specified context relative path.
+     * Save a reference to this page so that other utility methods may be used
+     * to retrieve information from it.</p>
+     *
+     * @param path Context relative path
+     *
+     * @exception IllegalArgumentException if the context relative path
+     *  does not begin with a '/' character
+     * @exception Exception if a different error occurs
+     */
+    protected HtmlPage page(String path) throws Exception
+    {
+
+        HtmlPage page = (HtmlPage) webClient.getPage(url(path));
+        this.page = page;
+        return (page);
+
+    }
+
+    /**
+     * <p>Reset the stored page reference to the specified value.  This is
+     * useful for scenarios testing resubmit of the same page (simulating the
+     * user pressing the back button and then submitting again).</p>
+     *
+     * @param page Previously saved page to which to reset
+     */
+    protected void reset(HtmlPage page)
+    {
+
+        this.page = page;
+
+    }
+
+    /**
+     * <p>Submit the current page, using the specified component, and retrieve
+     * the subsequent page, saving a reference so that other utility methods
+     * may be used to retrieve information from it.</p>
+     *
+     * @param submit Submit button component to click
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected HtmlPage submit(HtmlSubmitInput submit) throws IOException
+    {
+
+        HtmlPage page = (HtmlPage) submit.click();
+        this.page = page;
+        return page;
+
+    }
+
+    /**
+     * <p>Return the page title from the most recently retrieved page.
+     * Any leading and trailing whitespace will be trimmed.</p>
+     *
+     * @exception Exception if an error occurs
+     */
+    protected String title() throws Exception
+    {
+
+        return (page.getTitleText().trim());
+
+    }
+
+    /**
+     * <p>Calculate and return an absolute URL for the specified context
+     * relative path, which must begin with a '/' character.</p>
+     *
+     * @param path Context relative path
+     *
+     * @exception IllegalArgumentException if the context relative path
+     *  does not begin with a '/' character
+     * @exception Exception if a different error ocurs
+     */
+    protected URL url(String path) throws Exception
+    {
+
+        if (path.charAt(0) != '/')
+        {
+            throw new IllegalArgumentException("Context path '" + path
+                    + "' does not start with '/'");
+        }
+        return new URL(url, path.substring(1));
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/htmlunit/junit4/AbstractHtmlUnitTestCase.java b/test30/src/main/java/org/apache/myfaces/test/htmlunit/junit4/AbstractHtmlUnitTestCase.java
new file mode 100644
index 0000000..648b1de
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/htmlunit/junit4/AbstractHtmlUnitTestCase.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.myfaces.test.htmlunit.junit4;

+

+import java.io.IOException;

+import java.net.URL;

+import java.util.Iterator;

+

+import org.junit.After;

+import org.junit.Before;

+

+import com.gargoylesoftware.htmlunit.ElementNotFoundException;

+import com.gargoylesoftware.htmlunit.WebClient;

+import com.gargoylesoftware.htmlunit.html.HtmlAnchor;

+import com.gargoylesoftware.htmlunit.html.HtmlBody;

+import com.gargoylesoftware.htmlunit.html.HtmlElement;

+import com.gargoylesoftware.htmlunit.html.HtmlForm;

+import com.gargoylesoftware.htmlunit.html.HtmlHead;

+import com.gargoylesoftware.htmlunit.html.HtmlPage;

+import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;

+

+/**

+ * <p>Abstract base class for system integration tests based on HtmlUnit.

+ * These tests will expect a system property named <code>url</code> to be

+ * present, which will define the URL (including the context path, but

+ * without a trailing slash) of the application to be tested.</p>

+ * 

+ * @since 1.0.0

+ */

+public abstract class AbstractHtmlUnitTestCase

+{

+

+    // ------------------------------------------------------------ Constructors

+

+    /**

+     * <p>Construct a new instance of this test case.</p>

+     *

+     * @param name Name of the new test case

+     */

+    public AbstractHtmlUnitTestCase()

+    {

+    }

+

+    // ------------------------------------------------------ Instance Variables

+

+    /**

+     * <p>The most recently retrieved page from the server.</p>

+     */

+    protected HtmlPage page = null;

+

+    /**

+     * <p>The calculated URL for the installed "systest" web application.

+     * This value is based on a system property named <code>url</code>,

+     * which must be defined as part of the command line that executes

+     * each test case.</p>

+     */

+    protected URL url = null;

+

+    /**

+     * <p>The web client for this test case.</p>

+     */

+    protected WebClient webClient = null;

+

+    // ------------------------------------------------------ Test Setup Methods

+

+    /**

+     * <p>Set up the instance variables required for this test case.</p>

+     *

+     * @exception Exception if an error occurs

+     */

+    @Before

+    protected void setUp() throws Exception

+    {

+

+        // Calculate the URL for the installed "systest" web application

+        String url = System.getProperty("url");

+        this.url = new URL(url + "/");

+

+        // Initialize HtmlUnit constructs for this test case

+        webClient = new WebClient();

+

+    }

+

+    /**

+     * <p>Tear down instance variables required by this test case.</p>

+     */

+    @After

+    protected void tearDown() throws Exception

+    {

+

+        page = null;

+        url = null;

+        webClient = null;

+

+    }

+

+    // ------------------------------------------------------- Protected Methods

+

+    /**

+     * <p>Return the body element for the most recently retrieved page.

+     * If there is no such element, return <code>null</code>.</p>

+     *

+     * @exception Exception if an error occurs

+     */

+    protected HtmlBody body() throws Exception

+    {

+

+        Iterator elements = page.getAllHtmlChildElements();

+        while (elements.hasNext())

+        {

+            HtmlElement element = (HtmlElement) elements.next();

+            if (element instanceof HtmlBody)

+            {

+                return ((HtmlBody) element);

+            }

+        }

+        return (null);

+

+    }

+

+    /**

+     * <p>Return the HTML element with the specified <code>id</code> from the

+     * most recently retrieved page.  If there is no such element, return

+     * <code>null</code>.</p>

+     *

+     * @param id Identifier of the requested element.

+     *

+     * @exception Exception if an error occurs

+     */

+    protected HtmlElement element(String id) throws Exception

+    {

+

+        try

+        {

+            return (page.getHtmlElementById(id));

+        }

+        catch (ElementNotFoundException e)

+        {

+            return (null);

+        }

+

+    }

+

+    /**

+     * <p>Return the form with the specified <code>id</code> from the most

+     * recently retrieved page.  If there is no such form, return

+     * <code>null</code>.<p>

+     *

+     * @param id Identifier of the requested form.

+     *

+     * @exception Exception if an error occurs

+     */

+    protected HtmlForm form(String id) throws Exception

+    {

+

+        Iterator forms = page.getForms().iterator();

+        while (forms.hasNext())

+        {

+            HtmlForm form = (HtmlForm) forms.next();

+            if (id.equals(form.getAttributeValue("id")))

+            {

+                return (form);

+            }

+        }

+        return (null);

+

+    }

+

+    /**

+     * <p>Return the head element for the most recently retrieved page.

+     * If there is no such element, return <code>null</code>.</p>

+     *

+     * @exception Exception if an error occurs

+     */

+    protected HtmlHead head() throws Exception

+    {

+

+        Iterator elements = page.getAllHtmlChildElements();

+        while (elements.hasNext())

+        {

+            HtmlElement element = (HtmlElement) elements.next();

+            if (element instanceof HtmlHead)

+            {

+                return ((HtmlHead) element);

+            }

+        }

+        return (null);

+

+    }

+

+    /**

+     * <p>Click the specified hyperlink, and retrieve the subsequent page,

+     * saving a reference so that other utility methods may be used to

+     * retrieve information from it.</p>

+     *

+     * @param anchor Anchor component to click

+     *

+     * @exception IOException if an input/output error occurs

+     */

+    protected HtmlPage link(HtmlAnchor anchor) throws IOException

+    {

+

+        HtmlPage page = (HtmlPage) anchor.click();

+        this.page = page;

+        return page;

+

+    }

+

+    /**

+     * <p>Return the currently stored page reference.</p>

+     */

+    protected HtmlPage page()

+    {

+

+        return this.page;

+

+    }

+

+    /**

+     * <p>Retrieve and return the page at the specified context relative path.

+     * Save a reference to this page so that other utility methods may be used

+     * to retrieve information from it.</p>

+     *

+     * @param path Context relative path

+     *

+     * @exception IllegalArgumentException if the context relative path

+     *  does not begin with a '/' character

+     * @exception Exception if a different error occurs

+     */

+    protected HtmlPage page(String path) throws Exception

+    {

+

+        HtmlPage page = (HtmlPage) webClient.getPage(url(path));

+        this.page = page;

+        return (page);

+

+    }

+

+    /**

+     * <p>Reset the stored page reference to the specified value.  This is

+     * useful for scenarios testing resubmit of the same page (simulating the

+     * user pressing the back button and then submitting again).</p>

+     *

+     * @param page Previously saved page to which to reset

+     */

+    protected void reset(HtmlPage page)

+    {

+

+        this.page = page;

+

+    }

+

+    /**

+     * <p>Submit the current page, using the specified component, and retrieve

+     * the subsequent page, saving a reference so that other utility methods

+     * may be used to retrieve information from it.</p>

+     *

+     * @param submit Submit button component to click

+     *

+     * @exception IOException if an input/output error occurs

+     */

+    protected HtmlPage submit(HtmlSubmitInput submit) throws IOException

+    {

+

+        HtmlPage page = (HtmlPage) submit.click();

+        this.page = page;

+        return page;

+

+    }

+

+    /**

+     * <p>Return the page title from the most recently retrieved page.

+     * Any leading and trailing whitespace will be trimmed.</p>

+     *

+     * @exception Exception if an error occurs

+     */

+    protected String title() throws Exception

+    {

+

+        return (page.getTitleText().trim());

+

+    }

+

+    /**

+     * <p>Calculate and return an absolute URL for the specified context

+     * relative path, which must begin with a '/' character.</p>

+     *

+     * @param path Context relative path

+     *

+     * @exception IllegalArgumentException if the context relative path

+     *  does not begin with a '/' character

+     * @exception Exception if a different error ocurs

+     */

+    protected URL url(String path) throws Exception

+    {

+

+        if (path.charAt(0) != '/')

+        {

+            throw new IllegalArgumentException("Context path '" + path

+                    + "' does not start with '/'");

+        }

+        return new URL(url, path.substring(1));

+

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/jmock/AbstractJmockJsfTestCase.java b/test30/src/main/java/org/apache/myfaces/test/jmock/AbstractJmockJsfTestCase.java
new file mode 100644
index 0000000..2bd5f95
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/jmock/AbstractJmockJsfTestCase.java
@@ -0,0 +1,354 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.jmock;

+

+import java.net.URL;

+import java.net.URLClassLoader;

+

+import javax.faces.FactoryFinder;

+import javax.faces.application.ApplicationFactory;

+import javax.faces.component.UIViewRoot;

+import javax.faces.lifecycle.LifecycleFactory;

+import javax.faces.render.RenderKitFactory;

+

+import org.apache.myfaces.test.config.ResourceBundleVarNames;

+import org.apache.myfaces.test.mock.MockApplication;

+import org.apache.myfaces.test.mock.MockExternalContext;

+import org.apache.myfaces.test.mock.MockFacesContext;

+import org.apache.myfaces.test.mock.MockFacesContextFactory;

+import org.apache.myfaces.test.mock.MockHttpServletRequest;

+import org.apache.myfaces.test.mock.MockHttpServletResponse;

+import org.apache.myfaces.test.mock.MockHttpSession;

+import org.apache.myfaces.test.mock.MockRenderKit;

+import org.apache.myfaces.test.mock.MockServletConfig;

+import org.apache.myfaces.test.mock.MockServletContext;

+import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;

+import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;

+import org.jmock.cglib.MockObjectTestCase;

+

+/**

+ * <p>Abstract JMock test case base class, which sets up the JavaServer Faces

+ * mock object environment for a particular simulated request.  The following

+ * protected variables are initialized in the <code>setUp()</code> method, and

+ * cleaned up in the <code>tearDown()</code> method:</p>

+ * <ul>

+ * <li><code>application</code> (<code>MockApplication</code>)</li>

+ * <li><code>config</code> (<code>MockServletConfig</code>)</li>

+ * <li><code>externalContext</code> (<code>MockExternalContext</code>)</li>

+ * <li><code>facesContext</code> (<code>MockFacesContext</code>)</li>

+ * <li><code>lifecycle</code> (<code>MockLifecycle</code>)</li>

+ * <li><code>request</code> (<code>MockHttpServletRequest</code></li>

+ * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>

+ * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>

+ * <li><code>session</code> (<code>MockHttpSession</code>)</li>

+ * </ul>

+ *

+ * <p>In addition, appropriate factory classes will have been registered with

+ * <code>javax.faces.FactoryFinder</code> for <code>Application</code> and

+ * <code>RenderKit</code> instances.  The created <code>FacesContext</code>

+ * instance will also have been registered in the proper thread local

+ * variable, to simulate what a servlet container would do.</p>

+ *

+ * <p><strong>WARNING</strong> - If you choose to subclass this class, be sure

+ * your <code>setUp()</code> and <code>tearDown()</code> methods call

+ * <code>super.setUp()</code> and <code>super.tearDown()</code> respectively,

+ * and that you implement your own <code>suite()</code> method that exposes

+ * the test methods for your test case.</p>

+ * 

+ * @since 1.0.0

+ */

+

+public abstract class AbstractJmockJsfTestCase extends MockObjectTestCase

+{

+

+    // ------------------------------------------------------------ Constructors

+

+    /**

+     * <p>Construct a new instance of this test case.</p>

+     *

+     * @param name Name of this test case

+     */

+    public AbstractJmockJsfTestCase(String name)

+    {

+        setName(name);

+    }

+

+    // ---------------------------------------------------- Overall Test Methods

+

+    /**

+     * <p>Set up instance variables required by this test case.</p>

+     */

+    protected void setUp() throws Exception

+    {

+        // Set up a new thread context class loader

+        setUpClassloader();

+

+        // Set up Servlet API Objects

+        setUpServletObjects();

+

+        // Set up JSF API Objects

+        FactoryFinder.releaseFactories();

+

+        setFactories();

+

+        setUpJSFObjects();

+    }

+    

+    /**

+     * Set up the thread context classloader. JSF uses the this classloader

+     * in order to find related factory classes and other resources, but in

+     * some selected cases, the default classloader cannot be properly set.

+     * 

+     * @throws Exception 

+     */

+    protected void setUpClassloader() throws Exception

+    {

+        threadContextClassLoader = Thread.currentThread()

+                .getContextClassLoader();

+        Thread.currentThread()

+                .setContextClassLoader(

+                        new URLClassLoader(new URL[0], this.getClass()

+                                .getClassLoader()));

+        classLoaderSet = true;

+    }

+

+    /**

+     * <p>Setup JSF object used for the test. By default it calls to the following

+     * methods in this order:</p>

+     * 

+     * <ul>

+     * <li><code>setUpExternalContext();</code></li>

+     * <li><code>setUpLifecycle();</code></li>

+     * <li><code>setUpFacesContext();</code></li>

+     * <li><code>setUpView();</code></li>

+     * <li><code>setUpApplication();</code></li>

+     * <li><code>setUpRenderKit();</code></li>

+     * </ul>

+     * 

+     * @throws Exception

+     */

+    protected void setUpJSFObjects() throws Exception

+    {

+        setUpExternalContext();

+        setUpLifecycle();

+        setUpFacesContext();

+        setUpView();

+        setUpApplication();

+        setUpRenderKit();

+    }

+

+    /**

+     * <p>Setup servlet objects that will be used for the test:</p>

+     * 

+     * <ul>

+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>

+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>

+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>

+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>

+     * <li><code>session</code> (<code>MockHttpSession</code>)</li>

+     * </ul>

+     * 

+     * @throws Exception

+     */

+    protected void setUpServletObjects() throws Exception

+    {

+        servletContext = new MockServletContext();

+        config = new MockServletConfig(servletContext);

+        session = new MockHttpSession();

+        session.setServletContext(servletContext);

+        request = new MockHttpServletRequest(session);

+        request.setServletContext(servletContext);

+        response = new MockHttpServletResponse();

+    }

+

+    /**

+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>

+     * 

+     * @throws Exception

+     */

+    protected void setFactories() throws Exception

+    {

+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,

+                "org.apache.myfaces.test.mock.MockApplicationFactory");

+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,

+                "org.apache.myfaces.test.mock.MockFacesContextFactory");

+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,

+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");

+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,

+                "org.apache.myfaces.test.mock.MockRenderKitFactory");

+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,

+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");

+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,

+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");

+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,

+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");

+    }

+

+    /**

+     * Setup the <code>externalContext</code> variable, using the 

+     * servlet variables already initialized.

+     * 

+     * @throws Exception

+     */

+    protected void setUpExternalContext() throws Exception

+    {

+        externalContext = new MockExternalContext(servletContext, request,

+                response);

+    }

+

+    /**

+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>

+     * variables.

+     * 

+     * @throws Exception

+     */

+    protected void setUpLifecycle() throws Exception

+    {

+        lifecycleFactory = (MockLifecycleFactory) FactoryFinder

+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);

+        lifecycle = (MockLifecycle) lifecycleFactory

+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

+    }

+

+    /**

+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>

+     * variable. Before end, by default it override <code>externalContext</code>

+     * variable from the value retrieved from facesContext.getExternalContext(),

+     * because sometimes it is possible facesContext overrides externalContext

+     * internally.

+     * 

+     * @throws Exception

+     */

+    protected void setUpFacesContext() throws Exception

+    {

+        facesContextFactory = (MockFacesContextFactory) FactoryFinder

+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);

+        facesContext = (MockFacesContext) facesContextFactory.getFacesContext(

+                servletContext, request, response, lifecycle);

+        if (facesContext.getExternalContext() != null)

+        {

+            externalContext = (MockExternalContext) facesContext

+                    .getExternalContext();

+        }

+    }

+

+    /**

+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"

+     * and assign it to the current facesContext.

+     * 

+     * @throws Exception

+     */

+    protected void setUpView() throws Exception

+    {

+        UIViewRoot root = new UIViewRoot();

+        root.setViewId("/viewId");

+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);

+        facesContext.setViewRoot(root);

+    }

+

+    /**

+     * Setup the <code>application</code> variable and before

+     * the end by default it is assigned to the <code>facesContext</code>

+     * variable, calling <code>facesContext.setApplication(application)</code>

+     * 

+     * @throws Exception

+     */

+    protected void setUpApplication() throws Exception

+    {

+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder

+                .getFactory(FactoryFinder.APPLICATION_FACTORY);

+        application = (MockApplication) applicationFactory.getApplication();

+        facesContext.setApplication(application);

+    }

+

+    /**

+     * Setup the <code>renderKit</code> variable. This is a good place to use

+     * <code>ConfigParser</code> to register converters, validators, components

+     * or renderkits.

+     * 

+     * @throws Exception

+     */

+    protected void setUpRenderKit() throws Exception

+    {

+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder

+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);

+        renderKit = new MockRenderKit();

+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,

+                renderKit);

+    }

+

+    /**

+     * <p>Tear down instance variables required by this test case.</p>

+     */

+    protected void tearDown() throws Exception

+    {

+

+        application = null;

+        config = null;

+        externalContext = null;

+        if (facesContext != null)

+        {

+            facesContext.release();

+        }

+        facesContext = null;

+        lifecycle = null;

+        lifecycleFactory = null;

+        renderKit = null;

+        request = null;

+        response = null;

+        servletContext = null;

+        session = null;

+        FactoryFinder.releaseFactories();

+        ResourceBundleVarNames.resetNames();

+

+        tearDownClassloader();

+    }

+    

+    protected void tearDownClassloader() throws Exception

+    {

+        if (classLoaderSet)

+        {

+            Thread.currentThread().setContextClassLoader(threadContextClassLoader);

+            threadContextClassLoader = null;

+            classLoaderSet = false;

+        }

+    }

+

+    // ------------------------------------------------------ Instance Variables

+

+    // Mock object instances for our tests

+    protected MockApplication application = null;

+    protected MockServletConfig config = null;

+    protected MockExternalContext externalContext = null;

+    protected MockFacesContext facesContext = null;

+    protected MockFacesContextFactory facesContextFactory = null;

+    protected MockLifecycle lifecycle = null;

+    protected MockLifecycleFactory lifecycleFactory = null;

+    protected MockRenderKit renderKit = null;

+    protected MockHttpServletRequest request = null;

+    protected MockHttpServletResponse response = null;

+    protected MockServletContext servletContext = null;

+    protected MockHttpSession session = null;

+

+    // Thread context class loader saved and restored after each test

+    private ClassLoader threadContextClassLoader = null;

+    private boolean classLoaderSet = false;

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockActionListener.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockActionListener.java
new file mode 100644
index 0000000..3aa9937
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockActionListener.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.myfaces.test.mock;
+
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.ActionEvent;
+import javax.faces.event.ActionListener;
+
+/**
+ * <p>Mock implementation of the default <code>ActionListener</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockActionListener implements ActionListener
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockActionListener()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    // -------------------------------------------------- ActionListener Methods
+
+    /**
+     * <p>Process the specified <code>ActionEvent</code>.</p>
+     *
+     * @param event Event to be processed
+     *
+     * @exception AbortProcessingException if further event firing
+     *  should be skipped
+     */
+    public void processAction(ActionEvent event)
+            throws AbortProcessingException
+    {
+        // FIXME - provide default implementation
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication.java
new file mode 100644
index 0000000..e32f2a8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication.java
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.faces.FacesException;
+import javax.faces.application.Application;
+import javax.faces.application.NavigationHandler;
+import javax.faces.application.StateManager;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIComponent;
+import javax.faces.convert.Converter;
+import javax.faces.event.ActionListener;
+import javax.faces.render.RenderKitFactory;
+import javax.faces.validator.Validator;
+
+/**
+ * <p>Mock implementation of <code>Application</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockApplication extends Application
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockApplication()
+    {
+
+        setActionListener(new MockActionListener());
+        components = new HashMap();
+        converters = new HashMap();
+        converters1 = new HashMap();
+        setDefaultLocale(Locale.getDefault());
+        setDefaultRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        setNavigationHandler(new MockNavigationHandler());
+        setStateManager(new MockStateManager());
+        setSupportedLocales(new ArrayList());
+        validators = new HashMap();
+        setViewHandler(new MockViewHandler());
+
+        // Register the standard by-id converters
+        addConverter("javax.faces.BigDecimal",
+                "javax.faces.convert.BigDecimalConverter");
+        addConverter("javax.faces.BigInteger",
+                "javax.faces.convert.BigIntegerConverter");
+        addConverter("javax.faces.Boolean",
+                "javax.faces.convert.BooleanConverter");
+        addConverter("javax.faces.Byte", "javax.faces.convert.ByteConverter");
+        addConverter("javax.faces.Character",
+                "javax.faces.convert.CharacterConverter");
+        addConverter("javax.faces.DateTime",
+                "javax.faces.convert.DateTimeConverter");
+        addConverter("javax.faces.Double",
+                "javax.faces.convert.DoubleConverter");
+        addConverter("javax.faces.Float", "javax.faces.convert.FloatConverter");
+        addConverter("javax.faces.Integer",
+                "javax.faces.Convert.IntegerConverter");
+        addConverter("javax.faces.Long", "javax.faces.convert.LongConverter");
+        addConverter("javax.faces.Number",
+                "javax.faces.convert.NumberConverter");
+        addConverter("javax.faces.Short", "javax.faces.convert.ShortConverter");
+
+        // Register the standard by-type converters
+        addConverter(Boolean.class, "javax.faces.convert.BooleanConverter");
+        addConverter(Boolean.TYPE, "javax.faces.convert.BooleanConverter");
+        addConverter(Byte.class, "javax.faces.convert.ByteConverter");
+        addConverter(Byte.TYPE, "javax.faces.convert.ByteConverter");
+        addConverter(Character.class, "javax.faces.convert.CharacterConverter");
+        addConverter(Character.TYPE, "javax.faces.convert.CharacterConverter");
+        addConverter(Double.class, "javax.faces.convert.DoubleConverter");
+        addConverter(Double.TYPE, "javax.faces.convert.DoubleConverter");
+        addConverter(Float.class, "javax.faces.convert.FloatConverter");
+        addConverter(Float.TYPE, "javax.faces.convert.FloatConverter");
+        addConverter(Integer.class, "javax.faces.convert.IntegerConverter");
+        addConverter(Integer.TYPE, "javax.faces.convert.IntegerConverter");
+        addConverter(Long.class, "javax.faces.convert.LongConverter");
+        addConverter(Long.TYPE, "javax.faces.convert.LongConverter");
+        addConverter(Short.class, "javax.faces.convert.ShortConverter");
+        addConverter(Short.TYPE, "javax.faces.convert.ShortConverter");
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    private ActionListener actionListener = null;
+    private Map components = null;
+    private Map converters = null; // By id
+    private Map converters1 = null; // By type
+    private Locale defaultLocale = null;
+    private String defaultRenderKitId = null;
+    private String messageBundle = null;
+    private NavigationHandler navigationHandler = null;
+    private StateManager stateManager = null;
+    private Collection supportedLocales = null;
+    private Map validators = null;
+    private ViewHandler viewHandler = null;
+
+    // ----------------------------------------------------- Application Methods
+
+    /** {@inheritDoc} */
+    public ActionListener getActionListener()
+    {
+
+        return this.actionListener;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setActionListener(ActionListener actionListener)
+    {
+        this.actionListener = actionListener;
+    }
+
+    /** {@inheritDoc} */
+    public Locale getDefaultLocale()
+    {
+
+        return this.defaultLocale;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setDefaultLocale(Locale defaultLocale)
+    {
+
+        this.defaultLocale = defaultLocale;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getDefaultRenderKitId()
+    {
+
+        return this.defaultRenderKitId;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setDefaultRenderKitId(String defaultRenderKitId)
+    {
+
+        this.defaultRenderKitId = defaultRenderKitId;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getMessageBundle()
+    {
+
+        return this.messageBundle;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setMessageBundle(String messageBundle)
+    {
+
+        this.messageBundle = messageBundle;
+
+    }
+
+    /** {@inheritDoc} */
+    public NavigationHandler getNavigationHandler()
+    {
+
+        return this.navigationHandler;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setNavigationHandler(NavigationHandler navigationHandler)
+    {
+
+        this.navigationHandler = navigationHandler;
+
+    }
+
+    /** {@inheritDoc} */
+    public StateManager getStateManager()
+    {
+
+        return this.stateManager;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setStateManager(StateManager stateManager)
+    {
+
+        this.stateManager = stateManager;
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getSupportedLocales()
+    {
+
+        return this.supportedLocales.iterator();
+
+    }
+
+    /** {@inheritDoc} */
+    public void setSupportedLocales(Collection supportedLocales)
+    {
+
+        this.supportedLocales = supportedLocales;
+
+    }
+
+    /** {@inheritDoc} */
+    public ViewHandler getViewHandler()
+    {
+
+        return this.viewHandler;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setViewHandler(ViewHandler viewHandler)
+    {
+
+        this.viewHandler = viewHandler;
+
+    }
+
+    /** {@inheritDoc} */
+    public void addComponent(String componentType, String componentClass)
+    {
+
+        components.put(componentType, componentClass);
+
+    }
+
+    /** {@inheritDoc} */
+    public UIComponent createComponent(String componentType)
+    {
+
+        if (componentType == null)
+        {
+            throw new NullPointerException("Requested component type is null");
+        }
+        String componentClass = (String) components.get(componentType);
+        if (componentClass == null)
+        {
+            throw new FacesException(
+                    "No component class registered for component type '"
+                            + componentType + "'");
+        }
+        try
+        {
+            Class clazz = Class.forName(componentClass);
+            return ((UIComponent) clazz.newInstance());
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+
+    /** {@inheritDoc} */
+    public Iterator getComponentTypes()
+    {
+
+        return (components.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public void addConverter(String converterId, String converterClass)
+    {
+
+        converters.put(converterId, converterClass);
+
+    }
+
+    /** {@inheritDoc} */
+    public void addConverter(Class targetClass, String converterClass)
+    {
+
+        converters1.put(targetClass, converterClass);
+
+    }
+
+    /** {@inheritDoc} */
+    public Converter createConverter(String converterId)
+    {
+
+        String converterClass = (String) converters.get(converterId);
+        if (converterClass == null)
+        {
+            return null;
+        }
+        try
+        {
+            Class clazz = Class.forName(converterClass);
+            return ((Converter) clazz.newInstance());
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public Converter createConverter(Class targetClass)
+    {
+
+        String converterClass = (String) converters1.get(targetClass);
+        if (converterClass == null)
+        {
+            return null;
+        }
+        try
+        {
+            Class clazz = Class.forName(converterClass);
+            return ((Converter) clazz.newInstance());
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getConverterIds()
+    {
+
+        return (converters.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getConverterTypes()
+    {
+
+        return (converters1.keySet().iterator());
+
+    }
+
+
+    /** {@inheritDoc} */
+    public void addValidator(String validatorId, String validatorClass)
+    {
+
+        validators.put(validatorId, validatorClass);
+
+    }
+
+    /** {@inheritDoc} */
+    public Validator createValidator(String validatorId)
+    {
+
+        String validatorClass = (String) validators.get(validatorId);
+        try
+        {
+            Class clazz = Class.forName(validatorClass);
+            return ((Validator) clazz.newInstance());
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getValidatorIds()
+    {
+        return (validators.keySet().iterator());
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication12.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication12.java
new file mode 100644
index 0000000..d13da73
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication12.java
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import javax.el.ArrayELResolver;
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContextListener;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.ResourceBundleELResolver;
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.el.FacesImplicitObjectELResolver;
+import org.apache.myfaces.test.el.FacesResourceBundleELResolver;
+import org.apache.myfaces.test.el.FacesScopedAttributeELResolver;
+import org.apache.myfaces.test.el.MockExpressionFactory;
+import org.apache.myfaces.test.el.ReservedWordsELResolver;
+
+/**
+ * <p>Mock implementation of <code>Application</code> that includes the semantics
+ * added by JavaServer Faces 1.2.</p>
+ *
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockApplication12 extends MockApplication
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockApplication12()
+    {
+
+        super();
+
+        // Configure our expression factory and EL resolvers
+        expressionFactory = new MockExpressionFactory();
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>A list of resource bundles configured for this application.</p>
+     */
+    private Map bundles = new HashMap();
+
+    /**
+     * <p>The set of configured ELContextListener instances.</p>
+     */
+    private List elContextListeners = new ArrayList();
+
+    /**
+     * <p>Expression factory for this instance.</p>
+     */
+    private ExpressionFactory expressionFactory = null;
+
+    /**
+     * <p>The configured composite resolver to be returned by <code>getELResolver()</code>.
+     * This value is lazily instantiated.</p>
+     */
+    private ELResolver resolver = null;
+
+    /**
+     * <p>The set of ELResolver instances configured on this instance.</p>
+     */
+    private List resolvers = new ArrayList();
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add the specified resource bundle to those associated with
+     * this application.</p>
+     *
+     * @param name Name under which to add this resource bundle
+     * @param bundle ResourceBundle to add
+     */
+    public void addResourceBundle(String name, ResourceBundle bundle)
+    {
+        bundles.put(name, bundle);
+    }
+
+    /**
+     * <p>Return a <code>Map</code> of the resource bundles configured
+     * for this application, keyed by name.</p>
+     */
+    public Map getResourceBundles()
+    {
+        return bundles;
+    }
+
+    /**
+     * Set the current ExpressionFactory to be returned by this mock object
+     * 
+     * @param expressionFactory
+     */
+    public void setExpressionFactory(ExpressionFactory expressionFactory)
+    {
+        this.expressionFactory = expressionFactory;
+    }
+    
+    // ----------------------------------------------------- Application Methods
+
+    /** {@inheritDoc} */
+    public void addELContextListener(ELContextListener listener)
+    {
+
+        elContextListeners.add(listener);
+
+    }
+
+    /** {@inheritDoc} */
+    public void addELResolver(ELResolver resolver)
+    {
+
+        // Simulate the restriction that you cannot add resolvers after
+        // the first request has been processed.
+        if (this.resolver != null)
+        {
+            throw new IllegalStateException("Cannot add resolvers now");
+        }
+
+        resolvers.add(resolver);
+
+    }
+
+    /** {@inheritDoc} */
+    public UIComponent createComponent(ValueExpression expression,
+            FacesContext context, String componentType)
+    {
+
+        UIComponent component = null;
+        try
+        {
+            component = (UIComponent) expression.getValue(context
+                    .getELContext());
+            if (component == null)
+            {
+                component = createComponent(componentType);
+                expression.setValue(context.getELContext(), component);
+            }
+
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+        return component;
+
+    }
+
+    /** {@inheritDoc} */
+    public Object evaluateExpressionGet(FacesContext context,
+            String expression, Class expectedType) throws ELException
+    {
+
+        ValueExpression ve = getExpressionFactory().createValueExpression(
+                context.getELContext(), expression, expectedType);
+        return ve.getValue(context.getELContext());
+
+    }
+
+    /** {@inheritDoc} */
+    public ELContextListener[] getELContextListeners()
+    {
+
+        return (ELContextListener[]) elContextListeners
+                .toArray(new ELContextListener[elContextListeners.size()]);
+
+    }
+
+    /** {@inheritDoc} */
+    public ELResolver getELResolver()
+    {
+
+        if (resolver == null)
+        {
+
+            // Configure a default ELResolver per Section 5.6.2 of JSF 1.2
+            CompositeELResolver composite = new CompositeELResolver();
+
+            composite.add(new FacesImplicitObjectELResolver());
+
+            CompositeELResolver nested = new CompositeELResolver();
+            Iterator items = resolvers.iterator();
+            while (items.hasNext())
+            {
+                nested.add((ELResolver) items.next());
+            }
+            composite.add(nested);
+
+            // composite.add(new faces.ManagedBeanELResolver()); // FIXME
+            composite.add(new ResourceBundleELResolver());
+            composite.add(new FacesResourceBundleELResolver());
+            composite.add(new MapELResolver());
+            composite.add(new ListELResolver());
+            composite.add(new ArrayELResolver());
+            composite.add(new BeanELResolver());
+            composite.add(new FacesScopedAttributeELResolver());
+            composite.add(new ReservedWordsELResolver());
+
+            // Make the resolver we have configured the application wide one
+            resolver = composite;
+
+        }
+        return resolver;
+
+    }
+
+    /** {@inheritDoc} */
+    public ExpressionFactory getExpressionFactory()
+    {
+
+        return this.expressionFactory;
+
+    }
+
+    /** {@inheritDoc} */
+    public ResourceBundle getResourceBundle(FacesContext context, String name)
+    {
+
+        if ((context == null) || (name == null))
+        {
+            throw new NullPointerException();
+        }
+        Locale locale = null;
+        UIViewRoot viewRoot = context.getViewRoot();
+        if (viewRoot != null)
+        {
+            locale = viewRoot.getLocale();
+        }
+        if (locale == null)
+        {
+            locale = Locale.getDefault();
+        }
+        try
+        {
+            return ResourceBundle.getBundle(name, locale);
+        }
+        catch (MissingResourceException e)
+        {
+
+            String newName = ResourceBundleVarNames.getVarName(name);
+            if (newName == null)
+            {
+                return null;
+            }
+
+            try
+            {
+                return ResourceBundle.getBundle(newName, locale);
+            }
+            catch (MissingResourceException exc)
+            {
+                return null;
+            }
+
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void removeELContextListener(ELContextListener listener)
+    {
+
+        elContextListeners.remove(listener);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
new file mode 100644
index 0000000..ad24f1a
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
@@ -0,0 +1,1188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.beans.BeanDescriptor;
+import java.beans.BeanInfo;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.application.Application;
+import javax.faces.application.ProjectStage;
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceDependencies;
+import javax.faces.application.ResourceDependency;
+import javax.faces.application.ResourceHandler;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UINamingContainer;
+import javax.faces.component.UIOutput;
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.behavior.Behavior;
+import javax.faces.context.FacesContext;
+import javax.faces.convert.Converter;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.ComponentSystemEventListener;
+import javax.faces.event.ListenerFor;
+import javax.faces.event.ListenersFor;
+import javax.faces.event.SystemEvent;
+import javax.faces.event.SystemEventListener;
+import javax.faces.event.SystemEventListenerHolder;
+import javax.faces.render.Renderer;
+import javax.faces.validator.Validator;
+import javax.faces.view.ViewDeclarationLanguage;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.myfaces.test.mock.resource.MockResourceHandler;
+
+/**
+ * <p>Mock implementation of <code>Application</code> that includes the semantics
+ * added by JavaServer Faces 2.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockApplication20 extends MockApplication12
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockApplication20()
+    {
+        super();
+
+        // install the 2.0-ViewHandler-Mock
+        this.setViewHandler(new MockViewHandler20());
+        this.setResourceHandler(new MockResourceHandler());
+    }
+
+    private static class SystemListenerEntry
+    {
+        private List<SystemEventListener> _lstSystemEventListener;
+        private Map<Class<?>, List<SystemEventListener>> _sourceClassMap;
+
+        public SystemListenerEntry()
+        {
+        }
+
+        public void addListener(SystemEventListener listener)
+        {
+            assert listener != null;
+
+            addListenerNoDuplicate(getAnySourceListenersNotNull(), listener);
+        }
+
+        public void addListener(SystemEventListener listener, Class<?> source)
+        {
+            assert listener != null;
+
+            if (source == null)
+            {
+                addListener(listener);
+            }
+            else
+            {
+                addListenerNoDuplicate(
+                        getSpecificSourceListenersNotNull(source), listener);
+            }
+        }
+
+        public void removeListener(SystemEventListener listener)
+        {
+            assert listener != null;
+
+            if (_lstSystemEventListener != null)
+            {
+                _lstSystemEventListener.remove(listener);
+            }
+        }
+
+        public void removeListener(SystemEventListener listener,
+                Class<?> sourceClass)
+        {
+            assert listener != null;
+
+            if (sourceClass == null)
+            {
+                removeListener(listener);
+            }
+            else
+            {
+                if (_sourceClassMap != null)
+                {
+                    List<SystemEventListener> listeners = _sourceClassMap
+                            .get(sourceClass);
+                    if (listeners != null)
+                    {
+                        listeners.remove(listener);
+                    }
+                }
+            }
+        }
+
+        public void publish(Class<? extends SystemEvent> systemEventClass,
+                Class<?> classSource, Object source, SystemEvent event)
+        {
+            if (source != null && _sourceClassMap != null)
+            {
+                event = _traverseListenerList(_sourceClassMap.get(classSource),
+                        systemEventClass, source, event);
+            }
+
+            _traverseListenerList(_lstSystemEventListener, systemEventClass,
+                    source, event);
+        }
+
+        private void addListenerNoDuplicate(
+                List<SystemEventListener> listeners,
+                SystemEventListener listener)
+        {
+            if (!listeners.contains(listener))
+            {
+                listeners.add(listener);
+            }
+        }
+
+        private synchronized List<SystemEventListener> getAnySourceListenersNotNull()
+        {
+            if (_lstSystemEventListener == null)
+            {
+                /*
+                 * TODO: Check if modification occurs often or not, might have to use a synchronized list instead.
+                 * 
+                 * Registrations found:
+                 */
+                _lstSystemEventListener = new CopyOnWriteArrayList<SystemEventListener>();
+            }
+
+            return _lstSystemEventListener;
+        }
+
+        private synchronized List<SystemEventListener> getSpecificSourceListenersNotNull(
+                Class<?> sourceClass)
+        {
+            if (_sourceClassMap == null)
+            {
+                _sourceClassMap = new ConcurrentHashMap<Class<?>, List<SystemEventListener>>();
+            }
+
+            List<SystemEventListener> list = _sourceClassMap.get(sourceClass);
+            if (list == null)
+            {
+                /*
+                 * TODO: Check if modification occurs often or not, might have to use a synchronized list instead.
+                 * 
+                 * Registrations found:
+                 */
+                list = new CopyOnWriteArrayList<SystemEventListener>();
+                _sourceClassMap.put(sourceClass, list);
+            }
+
+            return list;
+        }
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private static final Log log = LogFactory.getLog(MockApplication20.class);
+
+    private final Map<Class<? extends SystemEvent>, SystemListenerEntry> _systemEventListenerClassMap = 
+        new ConcurrentHashMap<Class<? extends SystemEvent>, SystemListenerEntry>();
+
+    private Map<String, String> _defaultValidatorsIds = new HashMap<String, String>();
+
+    private ProjectStage _projectStage;
+
+    private final Map<String, Class<?>> _behaviorClassMap = new ConcurrentHashMap<String, Class<?>>();
+
+    private final Map<String, Class<?>> _validatorClassMap = new ConcurrentHashMap<String, Class<?>>();
+
+    private ResourceHandler _resourceHandler;
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    public Map<String, String> getDefaultValidatorInfo()
+    {
+        return Collections.unmodifiableMap(_defaultValidatorsIds);
+    }
+
+    private void _handleAnnotations(FacesContext context, Object inspected, UIComponent component)
+    {   
+        // determine the ProjectStage setting via the given FacesContext
+        // note that a local getProjectStage() could cause problems in wrapped environments
+        boolean isProduction = context.isProjectStage(ProjectStage.Production);
+        
+        Class<?> inspectedClass = inspected.getClass();
+        _handleListenerForAnnotations(context, inspected, inspectedClass, component, isProduction);
+
+        _handleResourceDependencyAnnotations(context, inspectedClass, component, isProduction);
+    }
+    
+    private void _handleListenerForAnnotations(FacesContext context, Object inspected, Class<?> inspectedClass, 
+        UIComponent component, boolean isProduction)
+    {
+        List<ListenerFor> listenerForList = null;
+        
+        if(listenerForList == null) //not in production or the class hasn't been inspected yet
+        {
+            ListenerFor listener = inspectedClass.getAnnotation(ListenerFor.class);
+            ListenersFor listeners = inspectedClass.getAnnotation(ListenersFor.class);
+            if(listener != null || listeners != null)
+            {
+                //listeners were found using one or both annotations, create and build a new list
+                listenerForList = new ArrayList<ListenerFor>();
+                
+                if(listener != null)
+                {
+                    listenerForList.add(listener);
+                }
+                
+                if(listeners != null)
+                {
+                    listenerForList.addAll(Arrays.asList(listeners.value()));
+                }
+            }
+        }        
+ 
+        if (listenerForList != null) //listeners were found through inspection or from cache, handle them
+        {
+            for (ListenerFor listenerFor : listenerForList)
+            {
+                _handleListenerFor(context, inspected, component, listenerFor);
+            }
+        }
+    }
+
+    private void _handleListenerFor(FacesContext context, Object inspected, UIComponent component,
+                                    ListenerFor annotation)
+    {
+        // If this annotation is not present on the class in question, no action must be taken.
+        if (annotation != null)
+        {
+            // Determine the "target" on which to call subscribeToEvent.
+            // If the class to which this annotation is attached implements ComponentSystemEventListener
+            if (inspected instanceof ComponentSystemEventListener)
+            {
+                // If the class to which this annotation is attached is a UIComponent instance, "target" is the
+                // UIComponent instance.
+
+                // If the class to which this annotation is attached is a Renderer instance, "target" is the
+                // UIComponent instance.
+
+                /*
+                 * If "target" is a UIComponent call UIComponent.subscribeToEvent(Class, ComponentSystemEventListener)
+                 * passing the systemEventClass() of the annotation as the first argument and the instance of the class
+                 * to which this annotation is attached (which must implement ComponentSystemEventListener) as the
+                 * second argument.
+                 */
+                component.subscribeToEvent(annotation.systemEventClass(), (ComponentSystemEventListener) inspected);
+            }
+            // If the class to which this annotation is attached implements SystemEventListener and does not implement
+            // ComponentSystemEventListener, "target" is the Application instance.
+            else if (component instanceof SystemEventListener)
+            {
+                // use the Application object from the FacesContext (note that a
+                // direct use of subscribeToEvent() could cause problems if the
+                // Application is wrapped)
+                Application application = context.getApplication();
+                
+                // If "target" is the Application instance, inspect the value of the sourceClass() annotation attribute
+                // value.
+                if (Void.class.equals(annotation.sourceClass()))
+                {
+                    /*
+                     * If the value is Void.class, call Application.subscribeToEvent(Class, SystemEventListener),
+                     * passing the value of systemEventClass() as the first argument and the instance of the class to
+                     * which this annotation is attached (which must implement SystemEventListener) as the second
+                     * argument.
+                     */
+                    application.subscribeToEvent(annotation.systemEventClass(), (SystemEventListener) inspected);
+                }
+                else
+                {
+                    /*
+                     * Otherwise, call Application.subscribeToEvent(Class, Class, SystemEventListener), passing the
+                     * value of systemEventClass() as the first argument, the value of sourceClass() as the second
+                     * argument, and the instance of the class to which this annotation is attached (which must
+                     * implement SystemEventListener) as the third argument.
+                     */
+                    application.subscribeToEvent(annotation.systemEventClass(), annotation.sourceClass(),
+                                     (SystemEventListener) inspected);
+                }
+            }
+
+            /*
+             * If the class to which this annotation is attached implements ComponentSystemEventListener and is neither
+             * an instance of Renderer nor UIComponent, the action taken is unspecified. This case must not trigger any
+             * kind of error.
+             */
+        }
+    }
+
+    private void _handleResourceDependencyAnnotations(FacesContext context, Class<?> inspectedClass, 
+        UIComponent component, boolean isProduction)
+    {
+        List<ResourceDependency> dependencyList = null;
+        
+        if(dependencyList == null)  //not in production or the class hasn't been inspected yet
+        {   
+            ResourceDependency dependency = inspectedClass.getAnnotation(ResourceDependency.class);
+            ResourceDependencies dependencies = inspectedClass.getAnnotation(ResourceDependencies.class);
+            if(dependency != null || dependencies != null)
+            {
+                //resource dependencies were found using one or both annotations, create and build a new list
+                dependencyList = new ArrayList<ResourceDependency>();
+                
+                if(dependency != null)
+                {
+                    dependencyList.add(dependency);
+                }
+                
+                if(dependencies != null)
+                {
+                    dependencyList.addAll(Arrays.asList(dependencies.value()));
+                }
+            }
+        }        
+ 
+        if (dependencyList != null) //resource dependencies were found through inspection or from cache, handle them
+        {
+            for (ResourceDependency dependency : dependencyList)
+            {
+                _handleResourceDependency(context, component, dependency);
+            }
+        }
+    }
+    
+    private void _handleResourceDependency(FacesContext context, UIComponent component, ResourceDependency annotation)
+    {
+        // If this annotation is not present on the class in question, no action must be taken.
+        if (annotation != null)
+        {
+            // Create a UIOutput instance by passing javax.faces.Output. to
+            // Application.createComponent(java.lang.String).
+            UIOutput output = (UIOutput) createComponent(UIOutput.COMPONENT_TYPE);
+
+            // Get the annotation instance from the class and obtain the values of the name, library, and
+            // target attributes.
+            String name = annotation.name();
+            if (name != null && name.length() > 0)
+            {
+                name = _ELText.parse(getExpressionFactory(), context.getELContext(), name).toString(
+                    context.getELContext());
+            }
+
+            // Obtain the renderer-type for the resource name by passing name to
+            // ResourceHandler.getRendererTypeForResourceName(java.lang.String).
+            // (note that we can not use this.getResourceHandler(), because the Application might be wrapped)
+            String rendererType = context.getApplication().getResourceHandler().getRendererTypeForResourceName(name);
+
+            // Call setRendererType on the UIOutput instance, passing the renderer-type.
+            output.setRendererType(rendererType);
+
+            // Obtain the Map of attributes from the UIOutput component by calling UIComponent.getAttributes().
+            Map<String, Object> attributes = output.getAttributes();
+
+            // Store the name into the attributes Map under the key "name".
+            attributes.put("name", name);
+
+            // If library is the empty string, let library be null.
+            String library = annotation.library();
+            if (library != null && library.length() > 0)
+            {
+                library = _ELText.parse(getExpressionFactory(), context.getELContext(), library).toString(
+                    context.getELContext());
+                // If library is non-null, store it under the key "library".
+                if ("this".equals(library))
+                {
+                    // Special "this" behavior
+                    Resource resource = (Resource)component.getAttributes().get(Resource.COMPONENT_RESOURCE_KEY);
+                    if (resource != null)
+                    {
+                        attributes.put("library", resource.getLibraryName());
+                    }
+                }
+                else
+                {
+                    attributes.put("library", library);
+                }
+            }
+
+            // If target is the empty string, let target be null.
+            String target = annotation.target();
+            if (target != null && target.length() > 0)
+            {
+                target = _ELText.parse(getExpressionFactory(), context.getELContext(), target).toString(
+                    context.getELContext());
+                // If target is non-null, store it under the key "target".
+                attributes.put("target", target);
+                context.getViewRoot().addComponentResource(context, output, target);
+            }
+            else
+            {
+                // Otherwise, if target is null, call UIViewRoot.addComponentResource(javax.faces.context.FacesContext,
+                // javax.faces.component.UIComponent), passing the UIOutput instance as the second argument.
+                context.getViewRoot().addComponentResource(context, output);
+            }
+        }
+    }
+    
+    private void _inspectRenderer(FacesContext context, UIComponent component, String componentType,
+        String rendererType)
+    {
+        /*
+         * The Renderer instance to inspect must be obtained by calling FacesContext.getRenderKit() and calling
+         * RenderKit.getRenderer(java.lang.String, java.lang.String) on the result, passing the argument componentFamily
+         * of the newly created component as the first argument and the argument rendererType as the second argument.
+         */
+        Renderer renderer = context.getRenderKit().getRenderer(component.getFamily(), rendererType);
+        if (renderer == null)
+        {
+            // If no such Renderer can be found, a message must be logged with a helpful error message.
+            log.error("renderer cannot be found for component type " + componentType + " and renderer type "
+                    + rendererType);
+        }
+        else
+        {
+            // Otherwise, UIComponent.setRendererType(java.lang.String) must be called on the newly created
+            // UIComponent instance, passing the argument rendererType as the argument.
+            component.setRendererType(rendererType);
+
+            /*
+             * except the Renderer for the component to be returned must be inspected for the annotations mentioned in
+             * createComponent(ValueExpression, FacesContext, String) as specified in the documentation for that method.
+             */
+            _handleAnnotations(context, renderer, component);
+        }
+    }
+    
+    private static SystemEvent _traverseListenerList(
+            List<? extends SystemEventListener> listeners,
+            Class<? extends SystemEvent> systemEventClass, Object source,
+            SystemEvent event)
+    {
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (SystemEventListener listener : listeners)
+            {
+                // Call SystemEventListener.isListenerForSource(java.lang.Object), passing the source argument.
+                // If this returns false, take no action on the listener.
+                if (listener.isListenerForSource(source))
+                {
+                    // Otherwise, if the event to be passed to the listener instances has not yet been constructed,
+                    // construct the event, passing source as the argument to the one-argument constructor that takes
+                    // an Object. This same event instance must be passed to all listener instances.
+                    event = _createEvent(systemEventClass, source, event);
+
+                    // Call SystemEvent.isAppropriateListener(javax.faces.event.FacesListener), passing the listener
+                    // instance as the argument. If this returns false, take no action on the listener.
+                    if (event.isAppropriateListener(listener))
+                    {
+                        // Call SystemEvent.processListener(javax.faces.event.FacesListener), passing the listener
+                        // instance.
+                        event.processListener(listener);
+                    }
+                }
+            }
+        }
+
+        return event;
+    }
+
+    private static SystemEvent _createEvent(
+            Class<? extends SystemEvent> systemEventClass, Object source,
+            SystemEvent event)
+    {
+        if (event == null)
+        {
+            try
+            {
+                Constructor<?>[] constructors = systemEventClass.getConstructors();
+                Constructor<? extends SystemEvent> constructor = null;
+                for (Constructor<?> c : constructors)
+                {
+                    if (c.getParameterTypes().length == 1)
+                    {
+                        // Safe cast, since the constructor belongs
+                        // to a class of type SystemEvent
+                        constructor = (Constructor<? extends SystemEvent>) c;
+                        break;
+                    }
+                }
+                if (constructor != null)
+                {
+                    event = constructor.newInstance(source);
+                }
+
+            }
+            catch (Exception e)
+            {
+                throw new FacesException("Couldn't instanciate system event of type " + systemEventClass.getName(), e);
+            }
+        }
+
+        return event;
+    }
+
+    private void checkNull(final Object param, final String paramName)
+    {
+        if (param == null)
+        {
+            throw new NullPointerException(paramName + " cannot be null.");
+        }
+    }
+
+    private void checkEmpty(final String param, final String paramName)
+    {
+        if (param.length() == 0)
+        {
+            throw new NullPointerException("String " + paramName
+                    + " cannot be empty.");
+        }
+    }
+
+    public void publishEvent(FacesContext facesContext,
+            Class<? extends SystemEvent> systemEventClass,
+            Class<?> sourceBaseType, Object source)
+    {
+        checkNull(systemEventClass, "systemEventClass");
+        checkNull(source, "source");
+        
+        //Call events only if event processing is enabled.
+        if (!facesContext.isProcessingEvents())
+        {
+            return;
+        }
+
+        try
+        {
+            SystemEvent event = null;
+            if (source instanceof SystemEventListenerHolder)
+            {
+                SystemEventListenerHolder holder = (SystemEventListenerHolder) source;
+
+                // If the source argument implements SystemEventListenerHolder, call 
+                // SystemEventListenerHolder.getListenersForEventClass(java.lang.Class) on it, 
+                // passing the systemEventClass 
+                // argument. If the list is not empty, perform algorithm traverseListenerList on the list.
+                event = _traverseListenerList(holder
+                        .getListenersForEventClass(systemEventClass),
+                        systemEventClass, source, event);
+            }
+            
+            UIViewRoot uiViewRoot = facesContext.getViewRoot();
+            if (uiViewRoot != null)
+            {
+                //Call listeners on view level
+                event = _traverseListenerList(uiViewRoot.getViewListenersForEventClass(systemEventClass), 
+                        systemEventClass, source, event);
+            }
+
+            SystemListenerEntry systemListenerEntry = _systemEventListenerClassMap
+                    .get(systemEventClass);
+            if (systemListenerEntry != null)
+            {
+                systemListenerEntry.publish(systemEventClass, sourceBaseType,
+                        source, event);
+            }
+        }
+        catch (AbortProcessingException e)
+        {
+            // If the act of invoking the processListener method causes an AbortProcessingException to be thrown, 
+            // processing of the listeners must be aborted, no further processing of the listeners for this event must 
+            // take place, and the exception must be logged with Level.SEVERE.
+            log.error("Event processing was aborted", e);
+        }
+    }
+
+    public void publishEvent(FacesContext facesContext,
+            Class<? extends SystemEvent> systemEventClass, Object source)
+    {
+        publishEvent(facesContext, systemEventClass, source.getClass(), source);
+    }
+
+    public ProjectStage getProjectStage()
+    {
+        // If the value has already been determined by a previous call to this
+        // method, simply return that value.
+        if (_projectStage == null)
+        {
+
+            FacesContext context = FacesContext.getCurrentInstance();
+            String stageName = context.getExternalContext().getInitParameter(
+                    ProjectStage.PROJECT_STAGE_PARAM_NAME);
+
+            // If a value is found found
+            if (stageName != null)
+            {
+                /*
+                 * see if an enum constant can be obtained by calling ProjectStage.valueOf(), passing the value from the
+                 * initParamMap. If this succeeds without exception, save the value and return it.
+                 */
+                try
+                {
+                    _projectStage = ProjectStage.valueOf(stageName);
+                    return _projectStage;
+                }
+                catch (IllegalArgumentException e)
+                {
+                    //log.log(Level.SEVERE, "Couldn't discover the current project stage", e);
+                }
+            }
+
+            _projectStage = ProjectStage.Production;
+        }
+
+        return _projectStage;
+    }
+
+    public void addBehavior(String behaviorId, String behaviorClass)
+    {
+        checkNull(behaviorId, "behaviorId");
+        checkEmpty(behaviorId, "behaviorId");
+        checkNull(behaviorClass, "behaviorClass");
+        checkEmpty(behaviorClass, "behaviorClass");
+
+        try
+        {
+            _behaviorClassMap.put(behaviorId, Class.forName(behaviorClass));
+        }
+        catch (ClassNotFoundException ignore)
+        {
+
+        }
+
+    }
+
+    public Iterator<String> getBehaviorIds()
+    {
+        return _behaviorClassMap.keySet().iterator();
+    }
+
+    public Behavior createBehavior(String behaviorId) throws FacesException
+    {
+        checkNull(behaviorId, "behaviorId");
+        checkEmpty(behaviorId, "behaviorId");
+
+        final Class<?> behaviorClass = this._behaviorClassMap.get(behaviorId);
+        if (behaviorClass == null)
+        {
+            throw new FacesException(
+                    "Could not find any registered behavior-class for behaviorId : "
+                            + behaviorId);
+        }
+
+        try
+        {
+            final Behavior behavior = (Behavior) behaviorClass.newInstance();
+            _handleAttachedResourceDependencyAnnotations(FacesContext.getCurrentInstance(), behaviorClass);            
+            return behavior;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException("Could not instantiate behavior: "
+                    + behaviorClass, e);
+        }
+    }
+
+    @Override
+    public void addValidator(String validatorId, String validatorClass)
+    {
+        super.addValidator(validatorId, validatorClass);
+
+        try
+        {
+            _validatorClassMap.put(validatorId, Class.forName(validatorClass));
+        }
+        catch (ClassNotFoundException ex)
+        {
+            throw new FacesException(ex.getMessage());
+        }
+
+    }
+
+    public void addDefaultValidatorId(String validatorId)
+    {
+        if (_validatorClassMap.containsKey(validatorId))
+        {
+            _defaultValidatorsIds.put(validatorId, _validatorClassMap.get(
+                    validatorId).getName());
+        }
+    }
+
+    public final ResourceHandler getResourceHandler()
+    {
+        return _resourceHandler;
+    }
+
+    public final void setResourceHandler(ResourceHandler resourceHandler)
+    {
+        checkNull(resourceHandler, "resourceHandler");
+
+        _resourceHandler = resourceHandler;
+    }
+
+    public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass,
+            SystemEventListener listener)
+    {
+        subscribeToEvent(systemEventClass, null, listener);
+    }
+
+    public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass,
+            Class<?> sourceClass, SystemEventListener listener)
+    {
+        checkNull(systemEventClass, "systemEventClass");
+        checkNull(listener, "listener");
+
+        SystemListenerEntry systemListenerEntry;
+        synchronized (_systemEventListenerClassMap)
+        {
+            systemListenerEntry = _systemEventListenerClassMap
+                    .get(systemEventClass);
+            if (systemListenerEntry == null)
+            {
+                systemListenerEntry = new SystemListenerEntry();
+                _systemEventListenerClassMap.put(systemEventClass,
+                        systemListenerEntry);
+            }
+        }
+
+        systemListenerEntry.addListener(listener, sourceClass);
+    }
+
+    public void unsubscribeFromEvent(
+            Class<? extends SystemEvent> systemEventClass,
+            SystemEventListener listener)
+    {
+        unsubscribeFromEvent(systemEventClass, null, listener);
+    }
+
+    public void unsubscribeFromEvent(
+            Class<? extends SystemEvent> systemEventClass,
+            Class<?> sourceClass, SystemEventListener listener)
+    {
+        checkNull(systemEventClass, "systemEventClass");
+        checkNull(listener, "listener");
+
+        SystemListenerEntry systemListenerEntry = _systemEventListenerClassMap
+                .get(systemEventClass);
+        if (systemListenerEntry != null)
+        {
+            systemListenerEntry.removeListener(listener, sourceClass);
+        }
+    }
+
+    @Override
+    public UIComponent createComponent(String componentType)
+    {
+        UIComponent component = super.createComponent(componentType);
+        _handleAnnotations(FacesContext.getCurrentInstance(), component, component);
+        return component;
+    }
+    
+
+    @Override
+    public UIComponent createComponent(ValueExpression componentExpression,
+                                       FacesContext facesContext, String componentType)
+            throws FacesException, NullPointerException
+    {
+
+        /*
+         * Before the component instance is returned, it must be inspected for the presence of a ListenerFor (or
+         * ListenersFor) or ResourceDependency (or ResourceDependencies) annotation. If any of these annotations are
+         * present, the action listed in ListenerFor or ResourceDependency must be taken on the component, before it is
+         * returned from this method. This variant of createComponent must not inspect the Renderer for the component to
+         * be returned for any of the afore mentioned annotations. Such inspection is the province of
+         */
+
+        checkNull(componentExpression, "componentExpression");
+        checkNull(facesContext, "facesContext");
+        checkNull(componentType, "componentType");
+
+        ELContext elContext = facesContext.getELContext();
+
+        try
+        {
+            Object retVal = componentExpression.getValue(elContext);
+
+            UIComponent createdComponent;
+
+            if (retVal instanceof UIComponent)
+            {
+                createdComponent = (UIComponent) retVal;
+                _handleAnnotations(facesContext, createdComponent, createdComponent);
+            }
+            else
+            {
+                createdComponent = createComponent(componentType);
+                componentExpression.setValue(elContext, createdComponent);
+            }
+
+            return createdComponent;
+        }
+        catch (FacesException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+    }
+
+    @Override
+    public UIComponent createComponent(ValueExpression componentExpression, FacesContext context, String componentType,
+                                       String rendererType)
+    {
+        // Like createComponent(ValueExpression, FacesContext, String)
+        UIComponent component = createComponent(componentExpression, context, componentType);
+
+        _inspectRenderer(context, component, componentType, rendererType);
+
+        return component;
+    }
+    
+
+    @Override
+    public UIComponent createComponent(FacesContext context, String componentType, String rendererType)
+    {
+        checkNull(context, "context");
+        checkNull(componentType, "componentType");
+
+        // Like createComponent(String)
+        UIComponent component = createComponent(componentType);
+
+        _inspectRenderer(context, component, componentType, rendererType);
+
+        return component;
+    }
+
+    @Override
+    public UIComponent createComponent(FacesContext context, Resource componentResource)
+    {
+        checkNull(context, "context");
+        checkNull(componentResource, "componentResource");
+        
+        UIComponent component = null;
+        Resource resource;
+        String fqcn;
+        Class<? extends UIComponent> componentClass = null;
+
+        /*
+         * Obtain a reference to the ViewDeclarationLanguage for this Application instance by calling
+         * ViewHandler.getViewDeclarationLanguage(javax.faces.context.FacesContext, java.lang.String), passing the
+         * viewId found by calling UIViewRoot.getViewId() on the UIViewRoot in the argument FacesContext.
+         */
+        UIViewRoot view = context.getViewRoot();
+        Application application = context.getApplication();
+        ViewDeclarationLanguage vdl = application.getViewHandler().getViewDeclarationLanguage(
+            context, view.getViewId());
+
+        /*
+         * Obtain a reference to the composite component metadata for this composite component by calling
+         * ViewDeclarationLanguage.getComponentMetadata(javax.faces.context.FacesContext,
+         * javax.faces.application.Resource), passing the facesContext and componentResource arguments to this method.
+         * This version of JSF specification uses JavaBeans as the API to the component metadata.
+         */
+        BeanInfo metadata = vdl.getComponentMetadata(context, componentResource);
+        if (metadata == null)
+        {
+            throw new FacesException("Could not get component metadata for " 
+                    + componentResource.getResourceName()
+                    + ". Did you forget to specify <composite:interface>?");
+        }
+
+        /*
+         * Determine if the component author declared a component-type for this component instance by obtaining the
+         * BeanDescriptor from the component metadata and calling its getValue() method, passing
+         * UIComponent.COMPOSITE_COMPONENT_TYPE_KEY as the argument. If non-null, the result must be a ValueExpression
+         * whose value is the component-type of the UIComponent to be created for this Resource component. Call through
+         * to createComponent(java.lang.String) to create the component.
+         */
+        BeanDescriptor descriptor = metadata.getBeanDescriptor();
+        ValueExpression componentType = (ValueExpression) descriptor.getValue(UIComponent.COMPOSITE_COMPONENT_TYPE_KEY);
+        boolean annotationsApplied = false;
+        if (componentType != null)
+        {
+            component = application.createComponent((String) componentType.getValue(context.getELContext()));
+            annotationsApplied = true;
+        }
+        else
+        {
+            /*
+             * Otherwise, determine if a script based component for this Resource can be found by calling
+             * ViewDeclarationLanguage.getScriptComponentResource(javax.faces.context.FacesContext,
+             * javax.faces.application.Resource). If the result is non-null, and is a script written in one of the
+             * languages listed in JSF 4.3 of the specification prose document, create a UIComponent instance from the
+             * script resource.
+             */
+            resource = vdl.getScriptComponentResource(context, componentResource);
+            if (resource != null)
+            {
+                String name = resource.getResourceName();
+                String className = name.substring(0, name.lastIndexOf('.'));
+                
+                Class clazz;
+                try
+                {
+                    clazz = Class.forName(className);
+                    component = (UIComponent)clazz.newInstance();
+                }
+                catch (Exception e)
+                {
+                    throw new FacesException(e);
+                }
+            }
+            else
+            {
+                /*
+                 * Otherwise, let library-name be the return from calling Resource.getLibraryName() on the argument
+                 * componentResource and resource-name be the return from calling Resource.getResourceName() on the
+                 * argument componentResource. Create a fully qualified Java class name by removing any file extension
+                 * from resource-name and let fqcn be library-name + "." + resource-name. If a class with the name of
+                 * fqcn cannot be found, take no action and continue to the next step. If any of InstantiationException,
+                 * IllegalAccessException, or ClassCastException are thrown, wrap the exception in a FacesException and
+                 * re-throw it. If any other exception is thrown, log the exception and continue to the next step.
+                 */
+
+                String name = componentResource.getResourceName();
+                String className = name.substring(0, name.lastIndexOf('.'));
+                fqcn = componentResource.getLibraryName() + "." + className;
+                
+                try
+                {
+                    componentClass = classForName(fqcn);
+                }
+                catch (ClassNotFoundException e)
+                {
+                }
+
+                if (componentClass != null)
+                {
+                    try
+                    {
+                        component = componentClass.newInstance();
+                    }
+                    catch (InstantiationException e)
+                    {
+                        log.error("Could not instantiate component class name = " + fqcn, e);
+                        throw new FacesException("Could not instantiate component class name = " + fqcn, e);
+                    }
+                    catch (IllegalAccessException e)
+                    {
+                        log.error("Could not instantiate component class name = " + fqcn, e);
+                        throw new FacesException("Could not instantiate component class name = " + fqcn, e);
+                    }
+                    catch (Exception e)
+                    {
+                        log.error("Could not instantiate component class name = " + fqcn, e);
+                    }
+                }
+
+                /*
+                 * If none of the previous steps have yielded a UIComponent instance, call
+                 * createComponent(java.lang.String) passing "javax.faces.NamingContainer" as the argument.
+                 */
+                if (component == null)
+                {
+                    component = application.createComponent(UINamingContainer.COMPONENT_TYPE);
+                    annotationsApplied = true;
+                }
+            }
+        }
+
+        /*
+         * Call UIComponent.setRendererType(java.lang.String) on the UIComponent instance, passing
+         * "javax.faces.Composite" as the argument.
+         */
+        component.setRendererType("javax.faces.Composite");
+
+        /*
+         * Store the argument Resource in the attributes Map of the UIComponent under the key,
+         * Resource.COMPONENT_RESOURCE_KEY.
+         */
+        component.getAttributes().put(Resource.COMPONENT_RESOURCE_KEY, componentResource);
+
+        /*
+         * Store composite component metadata in the attributes Map of the UIComponent under the key,
+         * UIComponent.BEANINFO_KEY.
+         */
+        component.getAttributes().put(UIComponent.BEANINFO_KEY, metadata);
+
+        /*
+         * Before the component instance is returned, it must be inspected for the presence of a ListenerFor annotation.
+         * If this annotation is present, the action listed in ListenerFor must be taken on the component, before it is
+         * returned from this method.
+         */
+        if (!annotationsApplied)
+        {
+            _handleAnnotations(context, component, component);
+        }
+
+        return component;
+    }
+    
+    private static Class classForName(String type)
+        throws ClassNotFoundException
+    {
+        if (type == null)
+        {
+            throw new NullPointerException("type");
+        }
+        try
+        {
+            // Try WebApp ClassLoader first
+            return Class.forName(type,
+                                 false, // do not initialize for faster startup
+                                 Thread.currentThread().getContextClassLoader());
+        }
+        catch (ClassNotFoundException ignore)
+        {
+            // fallback: Try ClassLoader for ClassUtils (i.e. the myfaces.jar lib)
+            return Class.forName(type,
+                                 false, // do not initialize for faster startup
+                                 MockApplication20.class.getClassLoader());
+        }
+    }
+    
+    private void _handleAttachedResourceDependencyAnnotations(FacesContext context, Object inspected)
+    {
+        if (inspected == null)
+        {
+            return;
+        }
+        
+        ResourceDependency annotation = inspected.getClass().getAnnotation(ResourceDependency.class);
+        
+        if (annotation == null)
+        {
+            // If the ResourceDependency annotation is not present, the argument must be inspected for the presence 
+            // of the ResourceDependencies annotation. 
+            ResourceDependencies dependencies = inspected.getClass().getAnnotation(ResourceDependencies.class);
+            if (dependencies != null)
+            {
+                // If the ResourceDependencies annotation is present, the action described in ResourceDependencies 
+                // must be taken.
+                for (ResourceDependency dependency : dependencies.value())
+                {
+                    _handleAttachedResourceDependency(context, dependency);
+                }
+            }
+        }
+        else
+        {
+            // If the ResourceDependency annotation is present, the action described in ResourceDependency must be 
+            // taken. 
+            _handleAttachedResourceDependency(context, annotation);
+        }
+    }
+    
+    private void _handleAttachedResourceDependency(FacesContext context, ResourceDependency annotation)
+    {
+        // If this annotation is not present on the class in question, no action must be taken. 
+        if (annotation != null)
+        {
+            Application application = context.getApplication();
+            
+            // Create a UIOutput instance by passing javax.faces.Output. to 
+            // Application.createComponent(java.lang.String).
+            UIOutput output = (UIOutput) application.createComponent(UIOutput.COMPONENT_TYPE);
+            
+            // Get the annotation instance from the class and obtain the values of the name, library, and 
+            // target attributes.
+            String name = annotation.name();
+            if (name != null && name.length() > 0)
+            {
+                name = _ELText.parse(getExpressionFactory(), context.getELContext(), name).toString(
+                    context.getELContext());
+            }
+            
+            // Obtain the renderer-type for the resource name by passing name to 
+            // ResourceHandler.getRendererTypeForResourceName(java.lang.String).
+            String rendererType = application.getResourceHandler().getRendererTypeForResourceName(name);
+            
+            // Call setRendererType on the UIOutput instance, passing the renderer-type.
+            output.setRendererType(rendererType);
+            
+            // Obtain the Map of attributes from the UIOutput component by calling UIComponent.getAttributes().
+            Map<String, Object> attributes = output.getAttributes();
+            
+            // Store the name into the attributes Map under the key "name".
+            attributes.put("name", name);
+            
+            // If library is the empty string, let library be null.
+            String library = annotation.library();
+            if (library != null && library.length() > 0)
+            {
+                library = _ELText.parse(getExpressionFactory(), context.getELContext(), library).toString(
+                    context.getELContext());
+                // If library is non-null, store it under the key "library".
+                attributes.put("library", library);
+            }
+            
+            // If target is the empty string, let target be null.
+            String target = annotation.target();
+            if (target != null && target.length() > 0)
+            {
+                target = _ELText.parse(getExpressionFactory(), context.getELContext(), target).toString(
+                    context.getELContext());
+                // If target is non-null, store it under the key "target".
+                attributes.put("target", target);
+                context.getViewRoot().addComponentResource(context, output, target);
+            }
+            else
+            {
+                // Otherwise, if target is null, call UIViewRoot.addComponentResource(javax.faces.context.FacesContext, 
+                // javax.faces.component.UIComponent), passing the UIOutput instance as the second argument.
+                context.getViewRoot().addComponentResource(context, output);
+            }
+        }
+    }
+
+    @Override
+    public Converter createConverter(String converterId)
+    {
+        Converter converter = super.createConverter(converterId);
+        _handleAttachedResourceDependencyAnnotations(FacesContext.getCurrentInstance(), converter);
+        return converter;
+    }
+
+    @Override
+    public Validator createValidator(String validatorId)
+    {
+        Validator validator = super.createValidator(validatorId);
+        _handleAttachedResourceDependencyAnnotations(FacesContext.getCurrentInstance(), validator);
+        return validator;
+    }
+    
+    
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication22.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication22.java
new file mode 100644
index 0000000..68dc315
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication22.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.myfaces.test.mock;
+
+import javax.faces.flow.FlowHandler;
+
+/**
+ * <p>Mock implementation of <code>Application</code> that includes the semantics
+ * added by JavaServer Faces 2.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockApplication22 extends MockApplication20
+{
+    // ------------------------------------------------------------ Constructors
+
+    public MockApplication22()
+    {
+        super();
+
+        // install flow Handler
+        setFlowHandler(new MockFlowHandler());
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private FlowHandler _flowHandler;
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    @Override
+    public FlowHandler getFlowHandler()
+    {
+        return _flowHandler;
+    }
+
+    @Override
+    public void setFlowHandler(FlowHandler flowHandler)
+    {
+        this._flowHandler = flowHandler;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication30.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication30.java
new file mode 100644
index 0000000..382c8f0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplication30.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.myfaces.test.mock;
+
+import javax.faces.flow.FlowHandler;
+
+/**
+ * <p>Mock implementation of <code>Application</code> that includes the semantics
+ * added by JavaServer Faces 3.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockApplication30 extends MockApplication22
+{
+    // ------------------------------------------------------------ Constructors
+
+    public MockApplication30()
+    {
+        super();
+
+        // install flow Handler
+        setFlowHandler(new MockFlowHandler());
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private FlowHandler _flowHandler;
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    @Override
+    public FlowHandler getFlowHandler()
+    {
+        return _flowHandler;
+    }
+
+    @Override
+    public void setFlowHandler(FlowHandler flowHandler)
+    {
+        this._flowHandler = flowHandler;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockApplicationFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplicationFactory.java
new file mode 100644
index 0000000..9a76fdd
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockApplicationFactory.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.myfaces.test.mock;
+
+import javax.faces.FacesException;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+
+/**
+ * <p>Mock implementation of <code>ApplicationFactory</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockApplicationFactory extends ApplicationFactory
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockApplicationFactory()
+    {
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The <code>Application</code> instance to be returned by
+     * this factory.</p>
+     */
+    private Application application = null;
+
+    // --------------------------------------------- AppolicationFactory Methods
+
+    /** {@inheritDoc} */
+    public Application getApplication()
+    {
+
+        if (this.application == null)
+        {
+            Class clazz = null;
+
+            try
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockApplication22");
+                if (clazz == null)
+                {
+                    clazz = this.getClass().getClassLoader().loadClass(
+                            "org.apache.myfaces.test.mock.MockApplication20");
+                }
+                this.application = (MockApplication) clazz.newInstance();
+            }
+            catch (NoClassDefFoundError e)
+            {
+                clazz = null; // We are not running in a JSF 1.2 environment
+            }
+            catch (ClassNotFoundException e)
+            {
+                clazz = null; // Same as above
+            }
+            catch (RuntimeException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new FacesException(e);
+            }
+
+            if (clazz == null)
+            {
+                try
+                {
+                    clazz = this.getClass().getClassLoader().loadClass(
+                            "org.apache.myfaces.test.mock.MockApplication12");
+                    this.application = (MockApplication) clazz.newInstance();
+                }
+                catch (NoClassDefFoundError e)
+                {
+                    clazz = null; // We are not running in a JSF 1.2 environment
+                }
+                catch (ClassNotFoundException e)
+                {
+                    clazz = null; // Same as above
+                }
+                catch (RuntimeException e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new FacesException(e);
+                }
+            }
+            if (clazz == null)
+            {
+                try
+                {
+                    clazz = this.getClass().getClassLoader().loadClass(
+                            "org.apache.myfaces.test.mock.MockApplication");
+                    this.application = (MockApplication) clazz.newInstance();
+                }
+                catch (RuntimeException e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new FacesException(e);
+                }
+            }
+        }
+        return this.application;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setApplication(Application application)
+    {
+
+        this.application = application;
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindow.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindow.java
new file mode 100644
index 0000000..4c2b539
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindow.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.myfaces.test.mock;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.faces.context.FacesContext;
+import javax.faces.lifecycle.ClientWindow;
+import javax.faces.render.ResponseStateManager;
+
+/**
+ * Mock ClientWindow implementation that by default set the id with "1".
+ * 
+ * @author Leonardo Uribe
+ */
+public class MockClientWindow extends ClientWindow
+{
+    private String windowId;
+    
+    private Map<String,String> queryParamsMap;
+
+    public MockClientWindow()
+    {
+    }
+    
+    @Override
+    public void decode(FacesContext context)
+    {
+        String windowId = calculateWindowId(context);
+
+        if (windowId != null)
+        {
+            // Store the current windowId.
+            setId(windowId);
+        }
+        else
+        {
+            //Generate a new windowId
+            setId("1");
+        }
+    }
+    
+    
+    protected String calculateWindowId(FacesContext context)
+    {
+        //1. If it comes as parameter, it takes precedence over any other choice, because
+        //   no browser is capable to do a POST and create a new window at the same time.
+        String windowId = context.getExternalContext().getRequestParameterMap().get(
+                ResponseStateManager.CLIENT_WINDOW_PARAM);
+        if (windowId != null)
+        {
+            return windowId;
+        }
+        windowId = context.getExternalContext().getRequestParameterMap().get(
+                ResponseStateManager.CLIENT_WINDOW_URL_PARAM);
+        if (windowId != null)
+        {
+            return windowId;
+        }
+        return null;
+    }
+
+    @Override
+    public String getId()
+    {
+        return this.windowId;
+    }
+    
+    public void setId(String id)
+    {
+        this.windowId = id;
+    }
+
+    @Override
+    public Map<String, String> getQueryURLParameters(FacesContext context)
+    {
+        if (queryParamsMap == null)
+        {
+            String id = context.getExternalContext().getClientWindow().getId();
+            if (id != null)
+            {
+                queryParamsMap = new HashMap<String, String>(2,1);
+                queryParamsMap.put(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, id);
+            }
+        }
+        return queryParamsMap;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindowFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindowFactory.java
new file mode 100644
index 0000000..6e57e32
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockClientWindowFactory.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.myfaces.test.mock;
+
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.lifecycle.ClientWindow;
+import javax.faces.lifecycle.ClientWindowFactory;
+
+/**
+ *
+ * @author lu4242
+ */
+public class MockClientWindowFactory extends ClientWindowFactory
+{
+    private static final String WINDOW_MODE_NONE = "none";
+    private static final String WINDOW_MODE_MOCK = "mock";
+    
+    private String windowMode;
+
+    @Override
+    public ClientWindow getClientWindow(FacesContext facesContext)
+    {
+        if (WINDOW_MODE_NONE.equals(getWindowMode(facesContext)))
+        {
+            //No need to do anything
+            return null;
+        }
+        else
+        {
+            if (WINDOW_MODE_MOCK.equals(getWindowMode(facesContext)))
+            {
+                return new MockClientWindow();
+            }
+            else
+            {
+                return null;
+            }
+        }
+    }    
+    
+    private String getWindowMode(FacesContext context)
+    {
+        if (windowMode == null)
+        {
+            windowMode = getStringInitParameter(
+                    context.getExternalContext(), 
+                    ClientWindow.CLIENT_WINDOW_MODE_PARAM_NAME, WINDOW_MODE_NONE);
+        }
+        return windowMode;
+    }
+    
+    private static String getStringInitParameter(ExternalContext context, String name, String defaultValue)
+    {
+        if (name == null)
+        {
+            throw new NullPointerException();
+        }
+        
+        String param = context.getInitParameter(name);
+        
+        if (param == null)
+        {
+            return defaultValue;
+        }
+
+        param = param.trim();
+        if (param.length() == 0)
+        {
+            return defaultValue;
+        }
+
+        return param;
+    }    
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockEnumeration.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockEnumeration.java
new file mode 100644
index 0000000..ea452e8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockEnumeration.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.myfaces.test.mock;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * <p>Mock implementation of an <code>Enumeration</code> wrapper around
+ * an <code>Iterator</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+class MockEnumeration implements Enumeration
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a wrapper instance.</p>
+     *
+     * @param iterator The <code>Iterator</code> to be wrapped
+     */
+    public MockEnumeration(Iterator iterator)
+    {
+
+        this.iterator = iterator;
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The <code>Iterator</code> we are wrapping.</p>
+     */
+    private Iterator iterator;
+
+    // ----------------------------------------------------- Enumeration Methods
+
+    /** {@inheritDoc} */
+    public boolean hasMoreElements()
+    {
+
+        return iterator.hasNext();
+
+    }
+
+    /** {@inheritDoc} */
+    public Object nextElement()
+    {
+
+        return iterator.next();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java
new file mode 100644
index 0000000..2e7a0c0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.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.myfaces.test.mock;
+
+import javax.el.ELException;
+import javax.faces.FacesException;
+import javax.faces.context.ExceptionHandler;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.ExceptionQueuedEvent;
+import javax.faces.event.ExceptionQueuedEventContext;
+import javax.faces.event.SystemEvent;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * <p>Mock implementation of <code>ExceptionHandler</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockExceptionHandler extends ExceptionHandler
+{
+
+    private Queue<ExceptionQueuedEvent> handled;
+    private Queue<ExceptionQueuedEvent> unhandled;
+    private ExceptionQueuedEvent handledAndThrown;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ExceptionQueuedEvent getHandledExceptionQueuedEvent()
+    {
+        return handledAndThrown;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterable<ExceptionQueuedEvent> getHandledExceptionQueuedEvents()
+    {
+        return handled == null ? Collections.<ExceptionQueuedEvent> emptyList()
+                : handled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Throwable getRootCause(Throwable t)
+    {
+        if (t == null)
+        {
+            throw new NullPointerException("t");
+        }
+
+        while (t != null)
+        {
+            Class<?> clazz = t.getClass();
+            if (!clazz.equals(FacesException.class)
+                    && !clazz.equals(ELException.class))
+            {
+                return t;
+            }
+
+            t = t.getCause();
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterable<ExceptionQueuedEvent> getUnhandledExceptionQueuedEvents()
+    {
+        return unhandled == null ? Collections
+                .<ExceptionQueuedEvent> emptyList() : unhandled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handle() throws FacesException
+    {
+        if (unhandled != null && !unhandled.isEmpty())
+        {
+            if (handled == null)
+            {
+                handled = new LinkedList<ExceptionQueuedEvent>();
+            }
+
+            FacesException toThrow = null;
+
+            do
+            {
+                // For each ExceptionEvent in the list
+
+                // get the event to handle
+                ExceptionQueuedEvent event = unhandled.peek();
+                try
+                {
+                    // call its getContext() method
+                    ExceptionQueuedEventContext context = event.getContext();
+
+                    // and call getException() on the returned result
+                    Throwable exception = context.getException();
+
+                    // Upon encountering the first such Exception that is not an instance of
+                    // javax.faces.event.AbortProcessingException
+                    if (!shouldSkip(exception))
+                    {
+                        // set handledAndThrown so that getHandledExceptionQueuedEvent() returns this event
+                        handledAndThrown = event;
+
+                        // Re-wrap toThrow in a ServletException or (PortletException, if in a portlet environment)
+                        // and throw it
+                        // FIXME: The spec says to NOT use a FacesException to propagate the exception, but I see
+                        //        no other way as ServletException is not a RuntimeException
+                        toThrow = wrap(getRethrownException(exception));
+                        break;
+                    }
+                }
+                catch (Throwable t)
+                {
+                    // A FacesException must be thrown if a problem occurs while performing
+                    // the algorithm to handle the exception
+                    throw new FacesException(
+                            "Could not perform the algorithm to handle the Exception",
+                            t);
+                }
+                finally
+                {
+                    // if we will throw the Exception or if we just logged it,
+                    // we handled it in either way --> add to handled
+                    handled.add(event);
+                    unhandled.remove(event);
+                }
+            } while (!unhandled.isEmpty());
+
+            // do we have to throw an Exception?
+            if (toThrow != null)
+            {
+                throw toThrow;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isListenerForSource(Object source)
+    {
+        return source instanceof ExceptionQueuedEventContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void processEvent(SystemEvent exceptionQueuedEvent)
+            throws AbortProcessingException
+    {
+        if (unhandled == null)
+        {
+            unhandled = new LinkedList<ExceptionQueuedEvent>();
+        }
+
+        unhandled.add((ExceptionQueuedEvent) exceptionQueuedEvent);
+    }
+
+    protected Throwable getRethrownException(Throwable exception)
+    {
+        // Let toRethrow be either the result of calling getRootCause() on the Exception,
+        // or the Exception itself, whichever is non-null
+        Throwable toRethrow = getRootCause(exception);
+        if (toRethrow == null)
+        {
+            toRethrow = exception;
+        }
+
+        return toRethrow;
+    }
+
+    protected FacesException wrap(Throwable exception)
+    {
+        if (exception instanceof FacesException)
+        {
+            return (FacesException) exception;
+        }
+        return new FacesException(exception);
+    }
+
+    protected boolean shouldSkip(Throwable exception)
+    {
+        return exception instanceof AbortProcessingException;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java
new file mode 100644
index 0000000..8072d1f
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.faces.context.ExceptionHandler;
+import javax.faces.context.ExceptionHandlerFactory;
+
+/**
+ * <p>Mock implementation of <code>ExceptionHandlerFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockExceptionHandlerFactory extends ExceptionHandlerFactory
+{
+
+    @Override
+    public ExceptionHandler getExceptionHandler()
+    {
+        return new MockExceptionHandler();
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext.java
new file mode 100644
index 0000000..1774fa0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext.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.myfaces.test.mock;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.faces.FacesException;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>Mock implementation of <code>ExternalContext</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockExternalContext extends ExternalContext
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a wrapper instance.</p>
+     *
+     * @param context <code>ServletContext</code> for this application
+     * @param request <code>HttpServetRequest</code> for this request
+     * @param response <code>HttpServletResponse</code> for this request
+     */
+    public MockExternalContext(ServletContext context,
+            HttpServletRequest request, HttpServletResponse response)
+    {
+
+        this.context = context;
+        this.request = request;
+        this.response = response;
+        this.applicationMap = null;
+        this.initParameterMap = null;
+        this.requestMap = null;
+        this.requestCookieMap = null;
+        this.requestHeaderMap = null;
+        this.requestParameterMap = null;
+        this.requestParameterValuesMap = null;
+        this.requestHeaderValuesMap = null;
+        this.sessionMap = null;
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    protected ServletContext context;
+    protected HttpServletRequest request;
+    protected HttpServletResponse response;
+    private Map applicationMap;
+    private Map initParameterMap;
+    private Map requestMap;
+    private Map requestCookieMap;
+    private Map requestHeaderMap;
+    private Map requestParameterMap;
+    private Map requestParameterValuesMap;
+    private Map requestHeaderValuesMap;
+    private Map sessionMap;
+
+    // ------------------------------------------------- setters for the mock object
+
+    /**
+     * <p>Add a new cookie for this request.</p>
+     *
+     * @param cookie The new cookie
+     */
+    public void addRequestCookieMap(Cookie cookie)
+    {
+        Map map = getRequestCookieMap();
+        if (request instanceof MockHttpServletRequest
+                && map instanceof _CookieMap)
+        {
+            ((MockHttpServletRequest) request).addCookie(cookie);
+        }
+        else
+        {
+            map.put(cookie.getName(), cookie);
+        }
+    }
+
+    /**
+     * <p>Set the request cookie map for this request.</p>
+     *
+     * @param map The new request cookie map
+     */
+    public void setRequestCookieMap(Map map)
+    {
+        requestCookieMap = map;
+    }
+
+    /**
+     * <p>Add the specified request parameter for this request.</p>
+     *
+     * @param key Parameter name
+     * @param value Parameter value
+     */
+    public void addRequestParameterMap(String key, String value)
+    {
+        Map map = getRequestParameterMap();
+        if (request instanceof MockHttpServletRequest
+                && map instanceof _RequestParameterMap)
+        {
+            ((MockHttpServletRequest) request).addParameter(key, value);
+        }
+        else
+        {
+            map.put(key, value);
+        }
+    }
+
+    /**
+     * <p>Set the request parameter map for this request.</p>
+     *
+     * @param map The new request parameter map
+     */
+    public void setRequestParameterMap(Map map)
+    {
+        requestParameterMap = map;
+    }
+
+    /**
+     * <p>Add the specified request header for this request.</p>
+     *
+     * @param key Parameter name
+     * @param value Parameter value
+     */
+    public void addRequestHeader(String key, String value)
+    {
+        Map map = getRequestHeaderMap();
+        if (request instanceof MockHttpServletRequest
+                && map instanceof _RequestHeaderMap)
+        {
+            ((MockHttpServletRequest) request).addHeader(key, value);
+        }
+        else
+        {
+            map.put(key, value);
+        }
+    }
+
+    /**
+     * <p>Set the request header map for this request.</p>
+     *
+     * @param map The new request header map
+     */
+    public void setRequestHeaderMap(Map map)
+    {
+        requestHeaderMap = map;
+    }
+
+    // ------------------------------------------------- ExternalContext Methods
+
+    /** {@inheritDoc} */
+    public void dispatch(String requestURI) throws IOException, FacesException
+    {
+        RequestDispatcher requestDispatcher = request
+                .getRequestDispatcher(requestURI);
+        // If there is no dispatcher, send NOT_FOUND
+        if (requestDispatcher == null)
+        {
+            ((HttpServletResponse) response)
+                    .sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        try
+        {
+            requestDispatcher.forward(request, response);
+        }
+        catch (ServletException e)
+        {
+            if (e.getMessage() != null)
+            {
+                throw new FacesException(e.getMessage(), e);
+            }
+            throw new FacesException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String encodeActionURL(String sb)
+    {
+        return sb;
+    }
+
+    /** {@inheritDoc} */
+    public String encodeNamespace(String aValue)
+    {
+        return aValue;
+    }
+
+    /** {@inheritDoc} */
+    public String encodeResourceURL(String sb)
+    {
+        return sb;
+    }
+
+    /** {@inheritDoc} */
+    public Map getApplicationMap()
+    {
+        if (applicationMap == null)
+        {
+            applicationMap = new _ApplicationMap(context);
+        }
+        return applicationMap;
+    }
+
+    /** {@inheritDoc} */
+    public String getAuthType()
+    {
+        return request.getAuthType();
+    }
+
+    /** {@inheritDoc} */
+    public Object getContext()
+    {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    public String getInitParameter(String name)
+    {
+        return context.getInitParameter(name);
+    }
+
+    /** {@inheritDoc} */
+    public Map getInitParameterMap()
+    {
+        if (initParameterMap == null)
+        {
+            initParameterMap = new _InitParameterMap(context);
+        }
+        return initParameterMap;
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteUser()
+    {
+        return request.getRemoteUser();
+    }
+
+    /** {@inheritDoc} */
+    public Object getRequest()
+    {
+        return request;
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestContextPath()
+    {
+        return request.getContextPath();
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestCookieMap()
+    {
+        if (requestCookieMap == null)
+        {
+            requestCookieMap = new _CookieMap(request);
+        }
+        return requestCookieMap;
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestHeaderMap()
+    {
+        if (requestHeaderMap == null)
+        {
+            requestHeaderMap = new _RequestHeaderMap(request);
+        }
+        return requestHeaderMap;
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestHeaderValuesMap()
+    {
+        if (requestHeaderValuesMap == null)
+        {
+            requestHeaderValuesMap = new _RequestHeaderValuesMap(request);
+        }
+        return requestHeaderValuesMap;
+    }
+
+    /** {@inheritDoc} */
+    public Locale getRequestLocale()
+    {
+        return request.getLocale();
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getRequestLocales()
+    {
+        return new LocalesIterator(request.getLocales());
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestMap()
+    {
+        if (requestMap == null)
+        {
+            requestMap = new _RequestMap(request);
+        }
+        return requestMap;
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestParameterMap()
+    {
+        if (requestParameterMap == null)
+        {
+            requestParameterMap = new _RequestParameterMap(request);
+        }
+        return requestParameterMap;
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getRequestParameterNames()
+    {
+        final Enumeration enumer = request.getParameterNames();
+        Iterator it = new Iterator()
+        {
+            public boolean hasNext()
+            {
+                return enumer.hasMoreElements();
+            }
+
+            public Object next()
+            {
+                return enumer.nextElement();
+            }
+
+            public void remove()
+            {
+                throw new UnsupportedOperationException(this.getClass()
+                        .getName()
+                        + " UnsupportedOperationException");
+            }
+        };
+        return it;
+    }
+
+    /** {@inheritDoc} */
+    public Map getRequestParameterValuesMap()
+    {
+        if (requestParameterValuesMap == null)
+        {
+            requestParameterValuesMap = new _RequestParameterValuesMap(request);
+        }
+        return requestParameterValuesMap;
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestPathInfo()
+    {
+        return request.getPathInfo();
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestServletPath()
+    {
+        return request.getServletPath();
+    }
+
+    /** {@inheritDoc} */
+    public URL getResource(String path) throws MalformedURLException
+    {
+        return context.getResource(path);
+    }
+
+    /** {@inheritDoc} */
+    public InputStream getResourceAsStream(String path)
+    {
+        return context.getResourceAsStream(path);
+    }
+
+    /** {@inheritDoc} */
+    public Set getResourcePaths(String path)
+    {
+        return context.getResourcePaths(path);
+    }
+
+    /** {@inheritDoc} */
+    public Object getResponse()
+    {
+        return response;
+    }
+
+    /** {@inheritDoc} */
+    public Object getSession(boolean create)
+    {
+        return request.getSession(create);
+    }
+
+    /** {@inheritDoc} */
+    public Map getSessionMap()
+    {
+        if (sessionMap == null)
+        {
+            sessionMap = new _SessionMap(request);
+        }
+        return sessionMap;
+    }
+
+    /** {@inheritDoc} */
+    public java.security.Principal getUserPrincipal()
+    {
+        return request.getUserPrincipal();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isUserInRole(String role)
+    {
+        return request.isUserInRole(role);
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message)
+    {
+        context.log(message);
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message, Throwable throwable)
+    {
+        context.log(message, throwable);
+    }
+
+    /** {@inheritDoc} */
+    public void redirect(String requestURI) throws IOException
+    {
+        response.sendRedirect(requestURI);
+        FacesContext.getCurrentInstance().responseComplete();
+    }
+    
+    public String encodeWebsocketURL(String baseUrl)
+    {
+        Integer port = 8080;
+        port = (port == 0) ? null : port;
+        if (port != null && 
+            !port.equals(request.getServerPort()))
+        {
+            String scheme = "http";
+            String serverName = request.getServerName();
+            String url;
+            try
+            {
+                url = new URL(scheme, serverName, port, baseUrl).toExternalForm();
+                url = url.replaceFirst("http", "ws");
+                return url;
+            }
+            catch (MalformedURLException ex)
+            {
+                //If cannot build the url, return the base one unchanged
+                return baseUrl;
+            }
+        }
+        else
+        {
+            return baseUrl;
+        }
+    }
+
+    /**
+     * <p>Iterator implementation that wraps an enumeration
+     * of Locales for the current request.</p>
+     */
+    private class LocalesIterator implements Iterator
+    {
+
+        /**
+         * <p>Construct an iterator wrapping the specified
+         * enumeration.</p>
+         *
+         * @param locales Locales enumeration to wrap
+         */
+        public LocalesIterator(Enumeration locales)
+        {
+            this.locales = locales;
+        }
+
+        /**
+         * <p>The enumeration to be wrapped.</p>
+         */
+        private Enumeration locales;
+
+        /** {@inheritDoc} */
+        public boolean hasNext()
+        {
+            return locales.hasMoreElements();
+        }
+
+        /** {@inheritDoc} */
+        public Object next()
+        {
+            return locales.nextElement();
+        }
+
+        /** {@inheritDoc} */
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext12.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext12.java
new file mode 100644
index 0000000..dbb9e91
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext12.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.UnsupportedEncodingException;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>Mock implementation of <code>ExternalContext</code> that includes the semantics
+ * added by JavaServer Faces 1.2.</p>
+ *
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockExternalContext12 extends MockExternalContext
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockExternalContext12(ServletContext context,
+            HttpServletRequest request, HttpServletResponse response)
+    {
+        super(context, request, response);
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------- ExternalContext Methods
+
+    /** {@inheritDoc} */
+    public String getRequestCharacterEncoding()
+    {
+
+        return this.request.getCharacterEncoding();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestContentType()
+    {
+
+        return this.request.getContentType();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getResponseCharacterEncoding()
+    {
+
+        return this.response.getCharacterEncoding();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getResponseContentType()
+    {
+
+        return this.response.getContentType();
+
+    }
+
+    /** {@inheritDoc} */
+    public void setRequest(Object request)
+    {
+
+        this.request = (HttpServletRequest) request;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setRequestCharacterEncoding(String encoding)
+            throws UnsupportedEncodingException
+    {
+
+        this.request.setCharacterEncoding(encoding);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setResponse(Object response)
+    {
+
+        this.response = (HttpServletResponse) response;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setResponseCharacterEncoding(String encoding)
+    {
+
+        this.response.setCharacterEncoding(encoding);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java
new file mode 100644
index 0000000..a83ff28
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.faces.context.Flash;
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * <p>Mock implementation of <code>ExternalContext</code> that includes the semantics
+ * added by JavaServer Faces 2.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockExternalContext20 extends MockExternalContext12
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockExternalContext20(ServletContext context,
+            HttpServletRequest request, HttpServletResponse response)
+    {
+        super(context, request, response);
+    }
+
+    public String getMimeType(String file)
+    {
+        return context.getMimeType(file);
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private static final String URL_PARAM_SEPERATOR = "&";
+    private static final String URL_QUERY_SEPERATOR = "?";
+    private static final String URL_FRAGMENT_SEPERATOR = "#";
+    private static final String URL_NAME_VALUE_PAIR_SEPERATOR = "=";
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------- ExternalContext Methods
+
+    public String encodeBookmarkableURL(String baseUrl,
+            Map<String, List<String>> parameters)
+    {
+        return response.encodeURL(encodeURL(baseUrl, parameters));
+    }
+
+    private String encodeURL(String baseUrl,
+            Map<String, List<String>> parameters)
+    {
+        String fragment = null;
+        String queryString = null;
+        Map<String, List<String>> paramMap = new HashMap<String, List<String>>();
+
+        //extract any URL fragment
+        int index = baseUrl.indexOf(URL_FRAGMENT_SEPERATOR);
+        if (index != -1)
+        {
+            fragment = baseUrl.substring(index + 1);
+            baseUrl = baseUrl.substring(0, index);
+        }
+
+        //extract the current query string and add the params to the paramMap
+        index = baseUrl.indexOf(URL_QUERY_SEPERATOR);
+        if (index != -1)
+        {
+            queryString = baseUrl.substring(index + 1);
+            baseUrl = baseUrl.substring(0, index);
+            String[] nameValuePairs = queryString.split(URL_PARAM_SEPERATOR);
+            for (int i = 0; i < nameValuePairs.length; i++)
+            {
+                String[] currentPair = nameValuePairs[i]
+                        .split(URL_NAME_VALUE_PAIR_SEPERATOR);
+                if (currentPair[1] != null)
+                {
+                    ArrayList<String> value = new ArrayList<String>(1);
+                    value.add(currentPair[1]);
+                    paramMap.put(currentPair[0], value);
+                }
+            }
+        }
+
+        //add/update with new params on the paramMap
+        if (parameters != null && parameters.size() > 0)
+        {
+            for (Map.Entry<String, List<String>> pair : parameters.entrySet())
+            {
+                if (pair.getKey() != null && pair.getKey().trim().length() != 0)
+                {
+                    paramMap.put(pair.getKey(), pair.getValue());
+                }
+            }
+        }
+
+        // start building the new URL
+        StringBuilder newUrl = new StringBuilder(baseUrl);
+
+        //now add the updated param list onto the url
+        if (paramMap.size() > 0)
+        {
+            boolean isFirstPair = true;
+            for (Map.Entry<String, List<String>> pair : paramMap.entrySet())
+            {
+                for (String value : pair.getValue())
+                {
+                    if (!isFirstPair)
+                    {
+                        newUrl.append(URL_PARAM_SEPERATOR);
+                    }
+                    else
+                    {
+                        newUrl.append(URL_QUERY_SEPERATOR);
+                        isFirstPair = false;
+                    }
+
+                    newUrl.append(pair.getKey());
+                    newUrl.append(URL_NAME_VALUE_PAIR_SEPERATOR);
+                    try
+                    {
+                        newUrl.append(URLEncoder.encode(value,
+                                getResponseCharacterEncoding()));
+                    }
+                    catch (UnsupportedEncodingException e)
+                    {
+                        //shouldn't ever get here
+                        throw new UnsupportedOperationException(
+                                "Encoding type="
+                                        + getResponseCharacterEncoding()
+                                        + " not supported", e);
+                    }
+                }
+            }
+        }
+
+        //add the fragment back on (if any)
+        if (fragment != null)
+        {
+            newUrl.append(URL_FRAGMENT_SEPERATOR + fragment);
+        }
+
+        return newUrl.toString();
+    }
+
+    public String encodeRedirectURL(String baseUrl,
+            Map<String, List<String>> parameters)
+    {
+        return response.encodeRedirectURL(encodeURL(baseUrl, parameters));
+    }
+
+    @Override
+    public String encodePartialActionURL(String url)
+    {
+        return ((HttpServletResponse) response).encodeURL(url);
+    }
+
+    public String getContextName()
+    {
+        return context.getServletContextName();
+    }
+
+    public String getRealPath(String path)
+    {
+        return context.getRealPath(path);
+    }
+
+    public void responseSendError(int statusCode, String message)
+            throws IOException
+    {
+        if (message == null)
+        {
+            response.sendError(statusCode);
+        }
+        else
+        {
+            response.sendError(statusCode, message);
+        }
+    }
+
+    public void setResponseHeader(String name, String value)
+    {
+        response.setHeader(name, value);
+    }
+
+    public String getRequestScheme()
+    {
+        return request.getScheme();
+    }
+
+    public String getRequestServerName()
+    {
+        return request.getServerName();
+    }
+
+    public int getRequestServerPort()
+    {
+        return request.getServerPort();
+    }
+
+    public OutputStream getResponseOutputStream() throws IOException
+    {
+        return response.getOutputStream();
+    }
+
+    public Writer getResponseOutputWriter() throws IOException
+    {
+        return response.getWriter();
+    }
+
+    @Override
+    public void setResponseContentType(String contentType)
+    {
+        response.setContentType(contentType);
+    }
+
+    public Flash getFlash()
+    {
+        return MockFlash.getCurrentInstance(this);
+    }
+
+    @Override
+    public void setResponseContentLength(int length)
+    {
+        response.setContentLength(length);
+    }
+
+    @Override
+    public int getRequestContentLength()
+    {
+        return request.getContentLength();
+    }
+
+    @Override
+    public int getResponseBufferSize()
+    {
+        return response.getBufferSize();
+    }
+
+    @Override
+    public void setResponseBufferSize(int size)
+    {
+        response.setBufferSize(size);
+    }
+
+    @Override
+    public void setResponseStatus(int statusCode)
+    {
+        response.setStatus(statusCode);
+    }
+
+    @Override
+    public void invalidateSession()
+    {
+        HttpSession session = request.getSession(false);
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+
+    @Override
+    public boolean isResponseCommitted()
+    {
+        return response.isCommitted();
+    }
+
+    @Override
+    public void responseFlushBuffer() throws IOException
+    {
+        response.flushBuffer();
+    }
+
+    @Override
+    public void responseReset()
+    {
+        response.reset();
+    }
+
+    @Override
+    public void addResponseCookie(String name, String value,
+            Map<String, Object> properties)
+    {
+        Cookie cookie = new Cookie(name, value);
+        if (properties != null)
+        {
+            for (Map.Entry<String, Object> entry : properties.entrySet())
+            {
+                String propertyKey = entry.getKey();
+                Object propertyValue = entry.getValue();
+                if ("comment".equals(propertyKey))
+                {
+                    cookie.setComment((String) propertyValue);
+                    continue;
+                }
+                else if ("domain".equals(propertyKey))
+                {
+                    cookie.setDomain((String)propertyValue);
+                    continue;
+                }
+                else if ("maxAge".equals(propertyKey))
+                {
+                    cookie.setMaxAge((Integer) propertyValue);
+                    continue;
+                }
+                else if ("secure".equals(propertyKey))
+                {
+                    cookie.setSecure((Boolean) propertyValue);
+                    continue;
+                }
+                else if ("path".equals(propertyKey))
+                {
+                    cookie.setPath((String) propertyValue);
+                    continue;
+                }
+                throw new IllegalArgumentException("Unused key when creating Cookie");
+            }
+        }
+        response.addCookie(cookie);
+    }
+
+    @Override
+    public void addResponseHeader(String name, String value)
+    {
+        response.addHeader(name, value);
+    }
+    
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext22.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext22.java
new file mode 100644
index 0000000..4d45ab9
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockExternalContext22.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.myfaces.test.mock;
+
+import javax.faces.lifecycle.ClientWindow;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * <p>Mock implementation of <code>ExternalContext</code> that includes the semantics
+ * added by JavaServer Faces 2.2.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockExternalContext22 extends MockExternalContext20
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockExternalContext22(ServletContext context,
+            HttpServletRequest request, HttpServletResponse response)
+    {
+        super(context, request, response);
+        _clientWindow = null;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private ClientWindow _clientWindow;
+    
+    // ----------------------------------------------------- Mock Object Methods
+
+
+    @Override
+    public boolean isSecure()
+    {
+        return request.isSecure();
+    }
+
+    @Override
+    public int getSessionMaxInactiveInterval()
+    {
+        HttpSession session = request.getSession();
+        return session.getMaxInactiveInterval();
+    }
+
+    @Override
+    public void setSessionMaxInactiveInterval(int interval)
+    {
+        HttpSession session = request.getSession();
+        session.setMaxInactiveInterval(interval);
+    }
+
+    @Override
+    public ClientWindow getClientWindow()
+    {
+        return _clientWindow;
+    }
+    
+    @Override
+    public void setClientWindow(ClientWindow window)
+    {
+        _clientWindow = window;
+    }
+
+    @Override
+    public String getSessionId(boolean create)
+    {
+        HttpSession session = ((HttpServletRequest) request).getSession(create);
+        if (session != null)
+        {
+            return session.getId();
+        }
+        else
+        {
+            return "";
+        }
+    }
+
+    @Override
+    public String getApplicationContextPath()
+    {
+        return context.getContextPath();
+    }
+
+    // ------------------------------------------------- ExternalContext Methods
+    
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java
new file mode 100644
index 0000000..e597361
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.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.myfaces.test.mock;

+

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+

+import javax.faces.FactoryFinder;

+import javax.faces.application.Application;

+import javax.faces.application.FacesMessage;

+import javax.faces.application.FacesMessage.Severity;

+import javax.faces.component.UIViewRoot;

+import javax.faces.context.ExternalContext;

+import javax.faces.context.FacesContext;

+import javax.faces.context.ResponseStream;

+import javax.faces.context.ResponseWriter;

+import javax.faces.lifecycle.Lifecycle;

+import javax.faces.render.RenderKit;

+import javax.faces.render.RenderKitFactory;

+

+/**

+ * <p>Mock implementation of <code>FacesContext</code>.</p>

+ *

+ * $Id$

+ * @since 1.0.0

+ */

+public class MockFacesContext extends FacesContext

+{

+

+    // ------------------------------------------------------------ Constructors

+

+    public MockFacesContext()

+    {

+        super();

+        setCurrentInstance(this);

+    }

+

+    public MockFacesContext(ExternalContext externalContext)

+    {

+        setExternalContext(externalContext);

+        setCurrentInstance(this);

+    }

+

+    public MockFacesContext(ExternalContext externalContext, Lifecycle lifecycle)

+    {

+        this(externalContext);

+        this.lifecycle = lifecycle;

+    }

+

+    // ----------------------------------------------------- Mock Object Methods

+

+    /**

+     * <p>Set the <code>Application</code> instance for this instance.</p>

+     *

+     * @param application The new Application

+     */

+    public void setApplication(Application application)

+    {

+

+        this.application = application;

+

+    }

+

+    /**

+     * <p>Set the <code>ExternalContext</code> instance for this instance.</p>

+     *

+     * @param externalContext The new ExternalContext

+     */

+    public void setExternalContext(ExternalContext externalContext)

+    {

+

+        this.externalContext = externalContext;

+

+    }

+

+    /**

+     * <p>Set the <code>FacesContext</code> instance for this instance.</p>

+     *

+     * @param facesContext The new FacesContext

+     */

+    public static void setCurrentInstance(FacesContext facesContext)

+    {

+

+        FacesContext.setCurrentInstance(facesContext);

+

+    }

+

+    // ------------------------------------------------------ Instance Variables

+

+    private Application application = null;

+    private ExternalContext externalContext = null;

+    private Lifecycle lifecycle = null;

+    protected Map messages = new HashMap(); // needs to be accessed in subclass MockFacesContext20

+    private boolean renderResponse = false;

+    private boolean responseComplete = false;

+    private ResponseStream responseStream = null;

+    private ResponseWriter responseWriter = null;

+    private UIViewRoot viewRoot = null;

+

+    // ---------------------------------------------------- FacesContext Methods

+

+    /** {@inheritDoc} */

+    public Application getApplication()

+    {

+

+        return this.application;

+

+    }

+

+    /** {@inheritDoc} */

+    public Iterator getClientIdsWithMessages()

+    {

+

+        return messages.keySet().iterator();

+

+    }

+

+    /** {@inheritDoc} */

+    public ExternalContext getExternalContext()

+    {

+

+        return this.externalContext;

+

+    }

+

+    /** {@inheritDoc} */

+    public Severity getMaximumSeverity()

+    {

+

+        Severity severity = null;

+        Iterator messages = getMessages();

+        while (messages.hasNext())

+        {

+            FacesMessage message = (FacesMessage) messages.next();

+            if (severity == null)

+            {

+                severity = message.getSeverity();

+            }

+            else if (message.getSeverity().getOrdinal() > severity.getOrdinal())

+            {

+                severity = message.getSeverity();

+            }

+        }

+        return severity;

+

+    }

+

+    /** {@inheritDoc} */

+    public Iterator getMessages()

+    {

+

+        ArrayList results = new ArrayList();

+        Iterator clientIds = messages.keySet().iterator();

+        while (clientIds.hasNext())

+        {

+            String clientId = (String) clientIds.next();

+            results.addAll((List) messages.get(clientId));

+        }

+        return results.iterator();

+

+    }

+

+    /** {@inheritDoc} */

+    public Iterator getMessages(String clientId)

+    {

+

+        List list = (List) messages.get(clientId);

+        if (list == null)

+        {

+            list = new ArrayList();

+        }

+        return list.iterator();

+

+    }

+

+    /** {@inheritDoc} */

+    public RenderKit getRenderKit()

+    {

+

+        UIViewRoot vr = getViewRoot();

+        if (vr == null)

+        {

+            return null;

+        }

+        String renderKitId = vr.getRenderKitId();

+        if (renderKitId == null)

+        {

+            return null;

+        }

+        RenderKitFactory rkFactory = (RenderKitFactory) FactoryFinder

+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);

+        return rkFactory.getRenderKit(this, renderKitId);

+

+    }

+

+    /** {@inheritDoc} */

+    public boolean getRenderResponse()

+    {

+

+        return this.renderResponse;

+

+    }

+

+    /** {@inheritDoc} */

+    public boolean getResponseComplete()

+    {

+

+        return this.responseComplete;

+

+    }

+

+    /** {@inheritDoc} */

+    public ResponseStream getResponseStream()

+    {

+

+        return this.responseStream;

+

+    }

+

+    /** {@inheritDoc} */

+    public void setResponseStream(ResponseStream responseStream)

+    {

+

+        this.responseStream = responseStream;

+

+    }

+

+    /** {@inheritDoc} */

+    public ResponseWriter getResponseWriter()

+    {

+

+        return this.responseWriter;

+

+    }

+

+    /** {@inheritDoc} */

+    public void setResponseWriter(ResponseWriter responseWriter)

+    {

+

+        this.responseWriter = responseWriter;

+

+    }

+

+    /** {@inheritDoc} */

+    public UIViewRoot getViewRoot()

+    {

+

+        return this.viewRoot;

+

+    }

+

+    /** {@inheritDoc} */

+    public void setViewRoot(UIViewRoot viewRoot)

+    {

+

+        this.viewRoot = viewRoot;

+

+    }

+

+    /** {@inheritDoc} */

+    public void addMessage(String clientId, FacesMessage message)

+    {

+

+        if (message == null)

+        {

+            throw new NullPointerException();

+        }

+        List list = (List) messages.get(clientId);

+        if (list == null)

+        {

+            list = new ArrayList();

+            messages.put(clientId, list);

+        }

+        list.add(message);

+

+    }

+

+    /** {@inheritDoc} */

+    public void release()

+    {

+

+        application = null;

+        externalContext = null;

+        messages.clear();

+        renderResponse = false;

+        responseComplete = false;

+        responseStream = null;

+        responseWriter = null;

+        viewRoot = null;

+        setCurrentInstance(null);

+

+    }

+

+    /** {@inheritDoc} */

+    public void renderResponse()

+    {

+

+        this.renderResponse = true;

+

+    }

+

+    /** {@inheritDoc} */

+    public void responseComplete()

+    {

+

+        this.responseComplete = true;

+

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext12.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext12.java
new file mode 100644
index 0000000..d92c589
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext12.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.myfaces.test.mock;
+
+import javax.el.ELContext;
+import javax.el.ELContextEvent;
+import javax.el.ELContextListener;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.lifecycle.Lifecycle;
+
+import org.apache.myfaces.test.el.MockELContext;
+
+/**
+ * <p>Mock implementation of <code>FacesContext</code> that includes the semantics
+ * added by JavaServer Faces 1.2.</p>
+ *
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockFacesContext12 extends MockFacesContext
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockFacesContext12()
+    {
+        super();
+        setCurrentInstance(this);
+    }
+
+    public MockFacesContext12(ExternalContext externalContext)
+    {
+        super(externalContext);
+    }
+
+    public MockFacesContext12(ExternalContext externalContext,
+            Lifecycle lifecycle)
+    {
+        super(externalContext, lifecycle);
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Set the <code>ELContext</code> instance for this instance.</p>
+     *
+     * @param elContext The new ELContext
+     */
+    public void setELContext(ELContext elContext)
+    {
+
+        this.elContext = elContext;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private ELContext elContext = null;
+
+    // ---------------------------------------------------- FacesContext Methods
+
+    /** {@inheritDoc} */
+    public ELContext getELContext()
+    {
+
+        if (this.elContext == null)
+        {
+
+            // Initialize a new ELContext
+            this.elContext = new MockELContext();
+            this.elContext.putContext(FacesContext.class, this);
+
+            // Notify interested listeners that this ELContext was created
+            ELContextListener[] listeners = getApplication()
+                    .getELContextListeners();
+            if ((listeners != null) && (listeners.length > 0))
+            {
+                ELContextEvent event = new ELContextEvent(this.elContext);
+                for (int i = 0; i < listeners.length; i++)
+                {
+                    listeners[i].contextCreated(event);
+                }
+            }
+
+        }
+        return this.elContext;
+
+    }
+
+    /** {@inheritDoc} */
+    public void release()
+    {
+        super.release();
+        this.elContext = null;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java
new file mode 100644
index 0000000..23fa4ee
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.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.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExceptionHandler;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.PartialViewContextFactory;
+import javax.faces.event.PhaseId;
+import javax.faces.lifecycle.Lifecycle;
+
+/**
+ * <p>Mock implementation of <code>FacesContext</code> that includes the semantics
+ * added by JavaServer Faces 2.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockFacesContext20 extends MockFacesContext12
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockFacesContext20()
+    {
+        super();
+        setCurrentInstance(this);
+    }
+
+    public MockFacesContext20(ExternalContext externalContext)
+    {
+        super(externalContext);
+    }
+
+    public MockFacesContext20(ExternalContext externalContext,
+            Lifecycle lifecycle)
+    {
+        super(externalContext, lifecycle);
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private boolean _processingEvents = true;
+    private ExceptionHandler _exceptionHandler = null;
+    private PhaseId _currentPhaseId = PhaseId.RESTORE_VIEW;
+    private boolean _postback;
+    private PartialViewContext _partialViewContext = null;
+    private Map<Object, Object> attributes;
+    private boolean _validationFailed = false;
+
+    // ----------------------------------------------------- Mock Object Methods   
+
+    public boolean isPostback()
+    {
+        return _postback;
+    }
+
+    public void setPostback(boolean value)
+    {
+        _postback = value;
+    }
+
+    public PhaseId getCurrentPhaseId()
+    {
+        return _currentPhaseId;
+    }
+
+    public void setCurrentPhaseId(PhaseId currentPhaseId)
+    {
+        this._currentPhaseId = currentPhaseId;
+    }
+
+    public Map<Object, Object> getAttributes()
+    {
+        if (attributes == null)
+        {
+            attributes = new HashMap<Object, Object>();
+        }
+        return attributes;
+    }
+
+    public PartialViewContext getPartialViewContext()
+    {
+        if (_partialViewContext == null)
+        {
+            //Get through factory finder
+            PartialViewContextFactory factory = (PartialViewContextFactory) FactoryFinder
+                    .getFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY);
+            _partialViewContext = factory.getPartialViewContext(this);
+        }
+        return _partialViewContext;
+    }
+
+    public boolean isProcessingEvents()
+    {
+        return _processingEvents;
+    }
+
+    public void setProcessingEvents(boolean processingEvents)
+    {
+        _processingEvents = processingEvents;
+    }
+
+    public ExceptionHandler getExceptionHandler()
+    {
+        return _exceptionHandler;
+    }
+
+    public void setExceptionHandler(ExceptionHandler exceptionHandler)
+    {
+        _exceptionHandler = exceptionHandler;
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<FacesMessage> getMessageList()
+    {
+        if (messages == null)
+        {
+            return Collections.unmodifiableList(Collections
+                    .<FacesMessage> emptyList());
+        }
+
+        List<FacesMessage> lst = new ArrayList<FacesMessage>();
+        for (List<FacesMessage> curLst : ((Map<String, List<FacesMessage>>) messages)
+                .values())
+        {
+            lst.addAll(curLst);
+        }
+
+        return Collections.unmodifiableList(lst);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<FacesMessage> getMessageList(String clientId)
+    {
+        if (messages == null || !messages.containsKey(clientId))
+        {
+            return Collections.unmodifiableList(Collections
+                    .<FacesMessage> emptyList());
+        }
+
+        return ((Map<String, List<FacesMessage>>) messages).get(clientId);
+    }
+
+    public boolean isValidationFailed()
+    {
+        return _validationFailed;
+    }
+
+    public void validationFailed()
+    {
+        _validationFailed = true;
+    }
+
+    // ------------------------------------------------- ExternalContext Methods
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext22.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext22.java
new file mode 100644
index 0000000..fcde8b8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContext22.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.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.faces.component.UINamingContainer;
+import javax.faces.context.ExternalContext;
+import javax.faces.lifecycle.Lifecycle;
+
+/**
+ * <p>Mock implementation of <code>FacesContext</code> that includes the semantics
+ * added by JavaServer Faces 2.0.</p>
+ * 
+ * @author Leonardo Uribe
+ * @since 1.0.0
+ *
+ */
+public class MockFacesContext22 extends MockFacesContext20
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockFacesContext22()
+    {
+        super();
+        setCurrentInstance(this);
+    }
+
+    public MockFacesContext22(ExternalContext externalContext)
+    {
+        super(externalContext);
+    }
+
+    public MockFacesContext22(ExternalContext externalContext,
+            Lifecycle lifecycle)
+    {
+        super(externalContext, lifecycle);
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private List<String> _resourceLibraryContracts;
+    private Character _separatorChar;
+    protected boolean _released = false;
+    
+    // ----------------------------------------------------- Mock Object Methods   
+
+    @Override
+    public List<String> getResourceLibraryContracts()
+    {
+        if (_resourceLibraryContracts == null)
+        {
+            return Collections.emptyList();
+        }
+        else
+        {
+            return _resourceLibraryContracts;
+        }
+    }
+    
+    @Override
+    public void setResourceLibraryContracts(List<String> contracts)
+    {
+        if (contracts == null)
+        {
+            _resourceLibraryContracts = null;
+        }
+        else if (contracts.isEmpty())
+        {
+            _resourceLibraryContracts = null;
+        }
+        else
+        {
+            _resourceLibraryContracts = new ArrayList<String>(contracts);
+        }
+    }
+    
+    @Override
+    public char getNamingContainerSeparatorChar()
+    {
+        if (_separatorChar == null)
+        {
+            _separatorChar = UINamingContainer.getSeparatorChar(this);
+        }
+        return _separatorChar;
+    }
+
+    // ------------------------------------------------- ExternalContext Methods
+
+    @Override
+    public boolean isReleased()
+    {
+        return _released;
+    }
+
+    @Override
+    public void release()
+    {
+        super.release();
+        _released = true;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContextFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContextFactory.java
new file mode 100644
index 0000000..0a62884
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFacesContextFactory.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.myfaces.test.mock;
+
+import java.lang.reflect.Constructor;
+import javax.faces.FacesException;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.FacesContextFactory;
+import javax.faces.lifecycle.Lifecycle;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>Mock implementation of <code>FacesContextFactory</code>.</p>
+ *
+ * $Id: MockFacesContextFactory.java 990408 2010-08-28 18:59:21Z lu4242 $
+ * @since 1.0.0
+ */
+
+public class MockFacesContextFactory extends FacesContextFactory
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Look up the constructor we will use for creating <code>MockFacesContext</code>
+     * instances.</p>
+     */
+    public MockFacesContextFactory()
+    {
+
+        Class clazz = null;
+
+        // Try to load the 2.2 version of our mock FacesContext class
+        try
+        {
+            clazz = this.getClass().getClassLoader().loadClass(
+                    "org.apache.myfaces.test.mock.MockFacesContext22");
+            constructor = clazz.getConstructor(facesContextSignature);
+            jsf22 = true;
+        }
+        catch (NoClassDefFoundError e)
+        {
+            // We are not running on JSF 2.0, so go to our fallback
+            clazz = null;
+            constructor = null;
+        }
+        catch (ClassNotFoundException e)
+        {
+            // Same as above
+            clazz = null;
+            constructor = null;
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+        
+        // Try to load the 2.0 version of our mock FacesContext class
+        try
+        {
+            if (clazz == null)
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockFacesContext20");
+                constructor = clazz.getConstructor(facesContextSignature);
+                jsf20 = true;
+            }
+        }
+        catch (NoClassDefFoundError e)
+        {
+            // We are not running on JSF 2.0, so go to our fallback
+            clazz = null;
+            constructor = null;
+        }
+        catch (ClassNotFoundException e)
+        {
+            // Same as above
+            clazz = null;
+            constructor = null;
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+        // Try to load the 1.2 version of our mock FacesContext class
+        try
+        {
+            if (clazz == null)
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockFacesContext12");
+                constructor = clazz.getConstructor(facesContextSignature);
+                jsf12 = true;
+            }
+        }
+        catch (NoClassDefFoundError e)
+        {
+            // We are not running on JSF 1.2, so go to our fallback
+            clazz = null;
+            constructor = null;
+        }
+        catch (ClassNotFoundException e)
+        {
+            // Same as above
+            clazz = null;
+            constructor = null;
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+        // Fall back to the 1.1 version if we could not load the 1.2 version
+        try
+        {
+            if (clazz == null)
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockFacesContext");
+                constructor = clazz.getConstructor(facesContextSignature);
+                jsf12 = false;
+            }
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The constructor for creating a <code>FacesContext</code> instance,
+     * taking an <code>ExternalContext</code> and <code>Lifecycle</code>.</p>
+     */
+    private Constructor constructor = null;
+
+    /**
+     * <p>The parameter signature of the ExternalContext constructor we wish to call.</p>
+     */
+    private static Class[] externalContextSignature = new Class[] {
+            ServletContext.class, HttpServletRequest.class,
+            HttpServletResponse.class };
+
+    /**
+     * <p>The parameter signature of the FacesContext constructor we wish to call.</p>
+     */
+    private static Class[] facesContextSignature = new Class[] {
+            ExternalContext.class, Lifecycle.class };
+
+    /**
+     * <p>Flag indicating that we are running in a JSF 1.2 environment.</p>
+     */
+    private boolean jsf12 = false;
+
+    /**
+     * <p>Flag indicating that we are running in a JSF 2.0 environment.</p>
+     */
+    private boolean jsf20 = false;
+    
+    /**
+     * <p>Flag indicating that we are running in a JSF 2.2 environment.</p>
+     */
+    private boolean jsf22 = false;
+
+    // --------------------------------------------- FacesContextFactory Methods
+
+    /** {@inheritDoc} */
+    public FacesContext getFacesContext(Object context, Object request,
+            Object response, Lifecycle lifecycle) throws FacesException
+    {
+
+        // Select the appropriate MockExternalContext implementation class
+        Class clazz = MockExternalContext.class;
+
+        if (jsf22)
+        {
+            try
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockExternalContext22");
+            }
+            catch (RuntimeException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new FacesException(e);
+            }
+        }
+        
+        if (jsf20)
+        {
+            try
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockExternalContext20");
+            }
+            catch (RuntimeException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new FacesException(e);
+            }
+        }
+
+        if (jsf12)
+        {
+            try
+            {
+                clazz = this.getClass().getClassLoader().loadClass(
+                        "org.apache.myfaces.test.mock.MockExternalContext12");
+            }
+            catch (RuntimeException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new FacesException(e);
+            }
+        }
+
+        // Select the constructor we wish to call
+        Constructor mecConstructor = null;
+        try
+        {
+            mecConstructor = clazz.getConstructor(externalContextSignature);
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+        // Construct an appropriate MockExternalContext instance
+        MockExternalContext externalContext = null;
+        try
+        {
+            externalContext = (MockExternalContext) mecConstructor
+                    .newInstance(new Object[] { context, request, response });
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+        // Construct an appropriate MockFacesContext instance and return it
+        try
+        {
+            return (MockFacesContext) constructor.newInstance(new Object[] {
+                    externalContext, lifecycle });
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException(e);
+        }
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFlash.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFlash.java
new file mode 100644
index 0000000..cb592b4
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFlash.java
@@ -0,0 +1,823 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.Flash;
+import javax.faces.event.PhaseId;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * <p>Mock implementation of <code>Flash</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockFlash extends Flash
+{
+
+    /**
+     * Key on app map to keep current instance
+     */
+    static protected final String FLASH_INSTANCE = MockFlash.class.getName()
+            + ".INSTANCE";
+
+    /**
+     * Key used to check if there is the current request will be or was redirected
+     */
+    static protected final String FLASH_REDIRECT = MockFlash.class.getName()
+            + ".REDIRECT";
+
+    /**
+     * Key used to check if this request should keep messages (like tomahawk sandbox RedirectTracker.
+     * Used when post-redirect-get pattern is used)
+     */
+    static protected final String FLASH_KEEP_MESSAGES = MockFlash.class
+            .getName()
+            + ".KEEP_MESSAGES";
+
+    static protected final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";
+
+    /**
+     * Session map prefix to flash maps
+     */
+    static protected final String FLASH_SCOPE_CACHE = MockFlash.class.getName()
+            + ".SCOPE";
+
+    static protected final String FLASH_CURRENT_MAP_CACHE = MockFlash.class
+            .getName()
+            + ".CURRENTMAP.CACHE";
+
+    static protected final String FLASH_CURRENT_MAP_KEY = MockFlash.class
+            .getName()
+            + ".CURRENTMAP.KEY";
+
+    static protected final String FLASH_POSTBACK_MAP_CACHE = MockFlash.class
+            .getName()
+            + ".POSTBACKMAP.CACHE";
+
+    static protected final String FLASH_POSTBACK_MAP_KEY = MockFlash.class
+            .getName()
+            + ".POSTBACKMAP.KEY";
+
+    static private final char SEPARATOR_CHAR = '.';
+
+    // the current token value
+    private final AtomicLong _count;
+
+    public MockFlash()
+    {
+        _count = new AtomicLong(_getSeed());
+    }
+
+    /**
+     * @return a cryptographically secure random number to use as the _count seed
+     */
+    private static long _getSeed()
+    {
+        SecureRandom rng;
+        try
+        {
+            // try SHA1 first
+            rng = SecureRandom.getInstance("SHA1PRNG");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            // SHA1 not present, so try the default (which could potentially not be
+            // cryptographically secure)
+            rng = new SecureRandom();
+        }
+
+        // use 48 bits for strength and fill them in
+        byte[] randomBytes = new byte[6];
+        rng.nextBytes(randomBytes);
+
+        // convert to a long
+        return new BigInteger(randomBytes).longValue();
+    }
+
+    /**
+     * @return the next token to be assigned to this request
+     */
+    protected String _getNextToken()
+    {
+        // atomically increment the value
+        long nextToken = _count.incrementAndGet();
+
+        // convert using base 36 because it is a fast efficient subset of base-64
+        return Long.toString(nextToken, 36);
+    }
+
+    /**
+     * Return a Flash instance from the application map
+     *
+     * @param context
+     * @return
+     */
+    public static Flash getCurrentInstance(ExternalContext context)
+    {
+        Map<String, Object> applicationMap = context.getApplicationMap();
+        Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);
+
+        synchronized (applicationMap)
+        {
+            if (flash == null)
+            {
+                flash = new MockFlash();
+                context.getApplicationMap().put(FLASH_INSTANCE, flash);
+            }
+        }
+        return flash;
+    }
+
+    /**
+     * Return a wrapper from the session map used to implement flash maps
+     * for more information see SubKeyMap doc
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> _getMapFromSession(FacesContext context,
+            String token, boolean createIfNeeded)
+    {
+        ExternalContext external = context.getExternalContext();
+        Object session = external.getSession(true);
+
+        Map<String, Object> map = null;
+
+        // Synchronize on the session object to ensure that
+        // we don't ever create two different caches
+        synchronized (session)
+        {
+            map = (Map<String, Object>) external.getSessionMap().get(token);
+            if ((map == null) && createIfNeeded)
+            {
+                map = new MockSubKeyMap<Object>(context.getExternalContext()
+                        .getSessionMap(), token);
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * Return the flash map created on this traversal. This one will be sent
+     * on next request, so it will be retrieved as postback map of the next
+     * request.
+     * <p/>
+     * Note it is supposed that FLASH_CURRENT_MAP_KEY is initialized before
+     * restore view phase (see doPrePhaseActions() for details).
+     *
+     * @param context
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    protected Map<String, Object> getCurrentRequestMap(FacesContext context)
+    {
+        Map<String, Object> requestMap = context.getExternalContext()
+                .getRequestMap();
+        Map<String, Object> map = (Map<String, Object>) requestMap
+                .get(FLASH_CURRENT_MAP_CACHE);
+        if (map == null)
+        {
+            String token = (String) requestMap.get(FLASH_CURRENT_MAP_KEY);
+            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
+            map = _getMapFromSession(context, fullToken, true);
+            requestMap.put(FLASH_CURRENT_MAP_CACHE, map);
+        }
+        return map;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Map<String, Object> getPostbackRequestMap(FacesContext context)
+    {
+        Map<String, Object> requestMap = context.getExternalContext()
+                .getRequestMap();
+        Map<String, Object> map = (Map<String, Object>) requestMap
+                .get(FLASH_POSTBACK_MAP_CACHE);
+        if (map == null)
+        {
+            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);
+            if (token == null && isRedirect())
+            {
+                // In post-redirect-get, request values are reset, so we need
+                // to get the postback key again.
+                token = _getPostbackMapKey(context.getExternalContext());
+            }
+            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
+            map = _getMapFromSession(context, fullToken, true);
+            requestMap.put(FLASH_POSTBACK_MAP_CACHE, map);
+        }
+        return map;
+    }
+
+    /**
+     * Get the proper map according to the current phase:
+     * <p/>
+     * Normal case:
+     * <p/>
+     * - First request, restore view phase (create a new one): current map n
+     * - First request, execute phase: Skipped
+     * - First request, render  phase: current map n
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * - Second request, execute phase: postback map n
+     * - Second request, render  phase: current map n+1
+     * <p/>
+     * Post Redirect Get case: Redirect is triggered by a call to setRedirect(true) from NavigationHandler
+     * or earlier using c:set tag.
+     * <p/>
+     * - First request, restore view phase (create a new one): current map n
+     * - First request, execute phase: Skipped
+     * - First request, render  phase: current map n
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * POST
+     * <p/>
+     * - Second request, execute phase: postback map n
+     * <p/>
+     * REDIRECT
+     * <p/>
+     * - NavigationHandler do the redirect, requestMap data lost, called Flash.setRedirect(true)
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * GET
+     * <p/>
+     * - Third  request, restore view phase (create a new one): current map n+1
+     * (isRedirect() should return true as javadoc says)
+     * - Third  request, execute phase: skipped
+     * - Third  request, render  phase: current map n+1
+     * <p/>
+     * In this way proper behavior is preserved even in the case of redirect, since the GET part is handled as
+     * the "render" part of the current traversal, keeping the semantic of flash object.
+     *
+     * @return
+     */
+    private Map<String, Object> getCurrentPhaseMap()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId())
+                || !facesContext.isPostback() || isRedirect())
+        {
+            return getCurrentRequestMap(facesContext);
+        }
+        else
+        {
+            return getPostbackRequestMap(facesContext);
+        }
+    }
+
+    private void _removeAllChildren(FacesContext facesContext)
+    {
+        Map<String, Object> map = getPostbackRequestMap(facesContext);
+
+        // Clear everything - note that because of naming conventions,
+        // this will in fact automatically recurse through all children
+        // grandchildren etc. - which is kind of a design flaw of SubKeyMap,
+        // but one we're relying on
+        map.clear();
+    }
+
+    /**
+     *
+     */
+    @Override
+    public void doPrePhaseActions(FacesContext facesContext)
+    {
+        Map<String, Object> requestMap = facesContext.getExternalContext()
+                .getRequestMap();
+
+        if (PhaseId.RESTORE_VIEW.equals(facesContext.getCurrentPhaseId()))
+        {
+            // Generate token and put on requestMap
+            // It is necessary to set this token always, because on the next request
+            // it should be possible to change postback map.
+            String currentToken = _getNextToken();
+            requestMap.put(FLASH_CURRENT_MAP_KEY, currentToken);
+
+            if (facesContext.isPostback())
+            {
+                //Retore token
+                String previousToken = _getPostbackMapKey(facesContext
+                        .getExternalContext());
+
+                if (previousToken != null)
+                {
+                    requestMap.put(FLASH_POSTBACK_MAP_KEY, previousToken);
+                }
+            }
+
+            if (isKeepMessages())
+            {
+                restoreMessages(facesContext);
+            }
+        }
+
+        //
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()))
+        {
+            // Put current map on next previous map
+            // but only if this request is not a redirect
+            if (!isRedirect())
+            {
+                _addPostbackMapKey(facesContext.getExternalContext());
+            }
+        }
+    }
+
+    @Override
+    public void doPostPhaseActions(FacesContext facesContext)
+    {
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()))
+        {
+            //Remove previous flash from session
+            Map<String, Object> requestMap = facesContext.getExternalContext()
+                    .getRequestMap();
+            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);
+
+            if (token != null)
+            {
+                _removeAllChildren(facesContext);
+            }
+
+            if (isKeepMessages())
+            {
+                saveMessages(facesContext);
+            }
+        }
+        else if (isRedirect()
+                && (facesContext.getResponseComplete() || facesContext
+                        .getRenderResponse()))
+        {
+            if (isKeepMessages())
+            {
+                saveMessages(facesContext);
+            }
+        }
+    }
+
+    private static class MessageEntry implements Serializable
+    {
+        private final Object clientId;
+        private final Object message;
+
+        public MessageEntry(Object clientId, Object message)
+        {
+            this.clientId = clientId;
+            this.message = message;
+        }
+    }
+
+    protected void saveMessages(FacesContext facesContext)
+    {
+        List<MessageEntry> messageList = null;
+
+        Iterator<String> iterClientIds = facesContext
+                .getClientIdsWithMessages();
+        while (iterClientIds.hasNext())
+        {
+            String clientId = (String) iterClientIds.next();
+            Iterator<FacesMessage> iterMessages = facesContext
+                    .getMessages(clientId);
+
+            while (iterMessages.hasNext())
+            {
+                FacesMessage message = iterMessages.next();
+
+                if (messageList == null)
+                {
+                    messageList = new ArrayList<MessageEntry>();
+                }
+                messageList.add(new MessageEntry(clientId, message));
+            }
+        }
+
+        if (messageList != null)
+        {
+            if (isRedirect())
+            {
+                getPostbackRequestMap(facesContext).put(
+                        FLASH_KEEP_MESSAGES_LIST, messageList);
+            }
+            else
+            {
+                getCurrentRequestMap(facesContext).put(
+                        FLASH_KEEP_MESSAGES_LIST, messageList);
+            }
+        }
+    }
+
+    protected void restoreMessages(FacesContext facesContext)
+    {
+        Map<String, Object> postbackMap = getPostbackRequestMap(facesContext);
+        List<MessageEntry> messageList = (List<MessageEntry>) postbackMap
+                .get(FLASH_KEEP_MESSAGES_LIST);
+
+        if (messageList != null)
+        {
+            Iterator iterMessages = messageList.iterator();
+
+            while (iterMessages.hasNext())
+            {
+                MessageEntry message = (MessageEntry) iterMessages.next();
+                facesContext.addMessage((String) message.clientId,
+                        (FacesMessage) message.message);
+            }
+
+            postbackMap.remove(FLASH_KEEP_MESSAGES_LIST);
+        }
+    }
+
+    //private void _addPreviousToken
+
+    /**
+     * Retrieve the postback map key
+     */
+    private String _getPostbackMapKey(ExternalContext externalContext)
+    {
+        String token = null;
+        Object response = externalContext.getResponse();
+        if (response instanceof HttpServletResponse)
+        {
+            //Use a cookie
+            Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(
+                    FLASH_POSTBACK_MAP_KEY);
+            if (cookie != null)
+            {
+                token = cookie.getValue();
+            }
+        }
+        else
+        {
+            //Use HttpSession or PortletSession object
+            Map<String, Object> sessionMap = externalContext.getSessionMap();
+            token = (String) sessionMap.get(FLASH_POSTBACK_MAP_KEY);
+        }
+        return token;
+    }
+
+    /**
+     * Take the current map key and store it as a postback key for the next request.
+     *
+     * @param externalContext
+     */
+    private void _addPostbackMapKey(ExternalContext externalContext)
+    {
+        Object response = externalContext.getResponse();
+        String token = (String) externalContext.getRequestMap().get(
+                FLASH_CURRENT_MAP_KEY);
+
+        //Use HttpSession or PortletSession object
+        Map<String, Object> sessionMap = externalContext.getSessionMap();
+        sessionMap.put(FLASH_POSTBACK_MAP_KEY, token);
+    }
+
+    /**
+     * For check if there is a redirect we to take into accout this points:
+     * <p/>
+     * 1. isRedirect() could be accessed many times during the same
+     * request.
+     * 2. According to Post-Redirect-Get pattern, we cannot
+     * ensure request scope values are preserved.
+     */
+    @Override
+    public boolean isRedirect()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+        Boolean redirect = (Boolean) requestMap.get(FLASH_REDIRECT);
+        if (redirect == null)
+        {
+            Object response = externalContext.getResponse();
+            if (response instanceof HttpServletResponse)
+            {
+                // Request values are lost after a redirect. We can create a
+                // temporal cookie to pass the params between redirect calls.
+                // It is better than use HttpSession object, because this cookie
+                // is never sent by the server.
+                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
+                        .get(FLASH_REDIRECT);
+                if (cookie != null)
+                {
+                    redirect = Boolean.TRUE;
+                    HttpServletResponse httpResponse = (HttpServletResponse) response;
+                    // A redirect happened, so it is safe to remove the cookie, setting
+                    // the maxAge to 0 seconds. The effect is we passed FLASH_REDIRECT param
+                    // to this request object
+                    cookie.setMaxAge(0);
+                    cookie.setValue(null);
+                    httpResponse.addCookie(cookie);
+                    requestMap.put(FLASH_REDIRECT, redirect);
+                }
+                else
+                {
+                    redirect = Boolean.FALSE;
+                }
+            }
+            else
+            {
+                // Note that on portlet world we can't create cookies,
+                // so we are forced to use the session map. Anyway,
+                // according to the Bridge implementation(for example see
+                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
+                // session object is created at start faces request
+                Map<String, Object> sessionMap = externalContext
+                        .getSessionMap();
+                redirect = (Boolean) sessionMap.get(FLASH_REDIRECT);
+                if (redirect != null)
+                {
+                    sessionMap.remove(FLASH_REDIRECT);
+                    requestMap.put(FLASH_REDIRECT, redirect);
+                }
+                else
+                {
+                    redirect = Boolean.FALSE;
+                }
+            }
+        }
+        return redirect;
+    }
+
+    @Override
+    public void setRedirect(boolean redirect)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+
+        Boolean previousRedirect = (Boolean) requestMap.get(FLASH_REDIRECT);
+        previousRedirect = (previousRedirect == null) ? Boolean.FALSE
+                : previousRedirect;
+
+        if (!previousRedirect.booleanValue() && redirect)
+        {
+            // This request contains a redirect. This condition is in general
+            // triggered by a NavigationHandler. After a redirect all request scope
+            // values get lost, so in order to preserve this value we need to
+            // pass it between request. One strategy is use a cookie that is never sent
+            // to the client. Other alternative is use the session map.
+            externalContext.getSessionMap().put(FLASH_REDIRECT, redirect);
+        }
+        requestMap.put(FLASH_REDIRECT, redirect);
+    }
+
+    /**
+     * In few words take a value from request scope map and put it on current request map,
+     * so it is visible on the next request.
+     */
+    @Override
+    public void keep(String key)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        Map<String, Object> requestMap = facesContext.getExternalContext()
+                .getRequestMap();
+        Object value = requestMap.get(key);
+        getCurrentRequestMap(facesContext).put(key, value);
+    }
+
+    /**
+     * This is just an alias for request scope map.
+     */
+    @Override
+    public void putNow(String key, Object value)
+    {
+        FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
+                .put(key, value);
+    }
+
+    @Override
+    public boolean isKeepMessages()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+        Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
+        if (keepMessages == null)
+        {
+            Object response = externalContext.getResponse();
+            if (response instanceof HttpServletResponse)
+            {
+                // Request values are lost after a redirect. We can create a
+                // temporal cookie to pass the params between redirect calls.
+                // It is better than use HttpSession object, because this cookie
+                // is never sent by the server.
+                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
+                        .get(FLASH_KEEP_MESSAGES);
+                if (cookie != null)
+                {
+                    keepMessages = Boolean.TRUE;
+                    HttpServletResponse httpResponse = (HttpServletResponse) response;
+                    // It is safe to remove the cookie, setting
+                    // the maxAge to 0 seconds. The effect is we passed FLASH_KEEP_MESSAGES param
+                    // to this request object
+                    cookie.setMaxAge(0);
+                    cookie.setValue(null);
+                    httpResponse.addCookie(cookie);
+                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+                }
+                else
+                {
+                    keepMessages = Boolean.FALSE;
+                }
+            }
+            else
+            {
+                // Note that on portlet world we can't create cookies,
+                // so we are forced to use the session map. Anyway,
+                // according to the Bridge implementation(for example see
+                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
+                // session object is created at start faces request
+                Map<String, Object> sessionMap = externalContext
+                        .getSessionMap();
+                keepMessages = (Boolean) sessionMap.get(FLASH_KEEP_MESSAGES);
+                if (keepMessages != null)
+                {
+                    sessionMap.remove(FLASH_KEEP_MESSAGES);
+                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+                }
+                else
+                {
+                    keepMessages = Boolean.FALSE;
+                }
+            }
+        }
+        return keepMessages;
+    }
+
+    /**
+     * If this property is true, the messages should be keep for the next request, no matter
+     * if it is a normal postback case or a post-redirect-get case.
+     */
+    @Override
+    public void setKeepMessages(boolean keepMessages)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+
+        Boolean previousKeepMessages = (Boolean) requestMap
+                .get(FLASH_KEEP_MESSAGES);
+        previousKeepMessages = (previousKeepMessages == null) ? Boolean.FALSE
+                : previousKeepMessages;
+
+        if (!previousKeepMessages.booleanValue() && keepMessages)
+        {
+            externalContext.getSessionMap().put(FLASH_KEEP_MESSAGES,
+                    keepMessages);
+        }
+        requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+    }
+
+    public void clear()
+    {
+        getCurrentPhaseMap().clear();
+    }
+
+    public boolean containsKey(Object key)
+    {
+        return getCurrentPhaseMap().containsKey(key);
+    }
+
+    public boolean containsValue(Object value)
+    {
+        return getCurrentPhaseMap().containsValue(value);
+    }
+
+    public Set<Entry<String, Object>> entrySet()
+    {
+        return getCurrentPhaseMap().entrySet();
+    }
+
+    public Object get(Object key)
+    {
+        if (key == null)
+        {
+            return null;
+        }
+
+        if ("keepMessages".equals(key))
+        {
+            return isKeepMessages();
+        }
+        else if ("redirect".equals(key))
+        {
+            return isRedirect();
+        }
+
+        FacesContext context = FacesContext.getCurrentInstance();
+        Map<String, Object> postbackMap = getPostbackRequestMap(context);
+        Object returnValue = null;
+
+        if (postbackMap != null)
+        {
+            returnValue = postbackMap.get(key);
+        }
+
+        return returnValue;
+    }
+
+    public boolean isEmpty()
+    {
+        return getCurrentPhaseMap().isEmpty();
+    }
+
+    public Set<String> keySet()
+    {
+        return getCurrentPhaseMap().keySet();
+    }
+
+    public Object put(String key, Object value)
+    {
+        if (key == null)
+        {
+            return null;
+        }
+
+        if ("keepMessages".equals(key))
+        {
+            Boolean booleanValue = convertToBoolean(value);
+            this.setKeepMessages(booleanValue);
+            return booleanValue;
+        }
+        else if ("redirect".equals(key))
+        {
+            Boolean booleanValue = convertToBoolean(value);
+            this.setRedirect(booleanValue);
+            return booleanValue;
+        }
+        else
+        {
+            Object returnValue = getCurrentPhaseMap().put(key, value);
+            return returnValue;
+        }
+    }
+
+    private Boolean convertToBoolean(Object value)
+    {
+        Boolean booleanValue;
+        if (value instanceof Boolean)
+        {
+            booleanValue = (Boolean) value;
+        }
+        else
+        {
+            booleanValue = Boolean.parseBoolean(value.toString());
+        }
+        return booleanValue;
+    }
+
+    public void putAll(Map<? extends String, ? extends Object> m)
+    {
+        getCurrentPhaseMap().putAll(m);
+    }
+
+    public Object remove(Object key)
+    {
+        return getCurrentPhaseMap().remove(key);
+    }
+
+    public int size()
+    {
+        return getCurrentPhaseMap().size();
+    }
+
+    public Collection<Object> values()
+    {
+        return getCurrentPhaseMap().values();
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockFlowHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockFlowHandler.java
new file mode 100644
index 0000000..969f669
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockFlowHandler.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.myfaces.test.mock;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.faces.context.FacesContext;
+import javax.faces.flow.Flow;
+import javax.faces.flow.FlowCallNode;
+import javax.faces.flow.FlowHandler;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+public class MockFlowHandler extends FlowHandler
+{
+
+    @Override
+    public Map<Object, Object> getCurrentFlowScope()
+    {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public Flow getFlow(FacesContext context, String definingDocumentId, String id)
+    {
+        return null;
+    }
+
+    @Override
+    public void addFlow(FacesContext context, Flow toAdd)
+    {
+    }
+
+    @Override
+    public Flow getCurrentFlow(FacesContext context)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean isActive(FacesContext context, String definingDocument, String id)
+    {
+        return false;
+    }
+
+    @Override
+    public void transition(FacesContext context, Flow sourceFlow, Flow targetFlow, 
+        FlowCallNode outboundCallNode, String toViewId)
+    {
+    }
+
+    @Override
+    public void clientWindowTransition(FacesContext context)
+    {
+    }
+
+    @Override
+    public String getLastDisplayedViewId(FacesContext context)
+    {
+        return null;
+    }
+
+    @Override
+    public void pushReturnMode(FacesContext context)
+    {
+    }
+
+    @Override
+    public void popReturnMode(FacesContext context)
+    {
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletRequest.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletRequest.java
new file mode 100644
index 0000000..ee61e8b
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletRequest.java
@@ -0,0 +1,1094 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+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.Vector;
+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.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+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.HttpSessionEvent;
+import javax.servlet.http.Part;
+
+/**
+ * <p>Mock implementation of <code>HttpServletContext</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockHttpServletRequest implements HttpServletRequest
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockHttpServletRequest()
+    {
+
+        super();
+
+    }
+
+    public MockHttpServletRequest(HttpSession session)
+    {
+
+        super();
+        setHttpSession(session);
+
+    }
+
+    public MockHttpServletRequest(String contextPath, String servletPath,
+            String pathInfo, String queryString)
+    {
+
+        super();
+        setPathElements(contextPath, servletPath, pathInfo, queryString);
+
+    }
+
+    public MockHttpServletRequest(String contextPath, String servletPath,
+            String pathInfo, String queryString, HttpSession session)
+    {
+
+        super();
+        setPathElements(contextPath, servletPath, pathInfo, queryString);
+        setHttpSession(session);
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a new listener instance that should be notified about
+     * attribute changes.</p>
+     *
+     * @param listener The new listener to register
+     */
+    public void addAttributeListener(ServletRequestAttributeListener listener)
+    {
+        attributeListeners.add(listener);
+    }
+
+    /**
+     * <p>Add a date-valued header for this request.</p>
+     *
+     * @param name Header name
+     * @param value Header value
+     */
+    public void addDateHeader(String name, long value)
+    {
+
+        headers.add(name + ": " + formatDate(value));
+
+    }
+
+    /**
+     * <p>Add a String-valued header for this request.</p>
+     *
+     * @param name Header name
+     * @param value Header value
+     */
+    public void addHeader(String name, String value)
+    {
+
+        headers.add(name + ": " + value);
+
+    }
+
+    /**
+     * <p>Add an integer-valued header for this request.</p>
+     *
+     * @param name Header name
+     * @param value Header value
+     */
+    public void addIntHeader(String name, int value)
+    {
+
+        headers.add(name + ": " + value);
+
+    }
+
+    /**
+     * <p>Add a request parameter for this request.</p>
+     *
+     * @param name Parameter name
+     * @param value Parameter value
+     */
+    public void addParameter(String name, String value)
+    {
+
+        String[] values = (String[]) parameters.get(name);
+        if (values == null)
+        {
+            String[] results = new String[] { value };
+            parameters.put(name, results);
+            return;
+        }
+        String[] results = new String[values.length + 1];
+        System.arraycopy(values, 0, results, 0, values.length);
+        results[values.length] = value;
+        parameters.put(name, results);
+
+    }
+
+    public void addCookie(Cookie c)
+    {
+        for (int i = 0; i < cookies.size(); i++)
+        {
+            if (((Cookie) cookies.get(i)).getName().equals(c.getName()))
+            {
+                cookies.set(i, c);
+                return;
+            }
+        }
+        cookies.add(c);
+    }
+
+    /**
+     * <p>Return the <code>ServletContext</code> associated with
+     * this request.</p>
+     */
+    public ServletContext getServletContext()
+    {
+
+        return this.servletContext;
+
+    }
+
+    /**
+     * <p>Set the <code>HttpSession</code> associated with this request.</p>
+     *
+     * @param session The new session
+     */
+    public void setHttpSession(HttpSession session)
+    {
+
+        this.session = session;
+
+    }
+
+    /**
+     * <p>Set the <code>Locale</code> associated with this request.</p>
+     *
+     * @param locale The new locale
+     */
+    public void setLocale(Locale locale)
+    {
+
+        this.locale = locale;
+
+    }
+
+    /**
+     * <p>Set the parsed path elements associated with this request.</p>
+     *
+     * @param contextPath The context path
+     * @param servletPath The servlet path
+     * @param pathInfo The extra path information
+     * @param queryString The query string
+     */
+    public void setPathElements(String contextPath, String servletPath,
+            String pathInfo, String queryString)
+    {
+
+        this.contextPath = contextPath;
+        this.servletPath = servletPath;
+        this.pathInfo = pathInfo;
+        this.queryString = queryString;
+
+    }
+
+    /**
+     * <p>Set the <code>ServletContext</code> associated with this request.</p>
+     *
+     * @param servletContext The new servlet context
+     */
+    public void setServletContext(ServletContext servletContext)
+    {
+
+        this.servletContext = servletContext;
+
+    }
+
+    /**
+     * <p>Set the <code>Principal</code> associated with this request.</p>
+     *
+     * @param principal The new Principal
+     */
+    public void setUserPrincipal(Principal principal)
+    {
+
+        this.principal = principal;
+
+    }
+    
+    public void setMethod(String method)
+    {
+        this.method = method;
+    }
+    
+    protected MockWebContainer getWebContainer()
+    {
+        if (this.servletContext instanceof MockServletContext)
+        {
+            return ((MockServletContext)this.servletContext).getWebContainer();
+        }
+        return null;
+    }
+    
+    /**
+     * @param contextPath the contextPath to set
+     */
+    public void setContextPath(String contextPath)
+    {
+        this.contextPath = contextPath;
+    }
+
+    /**
+     * @param pathInfo the pathInfo to set
+     */
+    public void setPathInfo(String pathInfo)
+    {
+        this.pathInfo = pathInfo;
+    }
+
+    /**
+     * @param servletPath the servletPath to set
+     */
+    public void setServletPath(String servletPath)
+    {
+        this.servletPath = servletPath;
+    }
+    
+    public void addUserRole(String role)
+    {
+        if (this.roles == null)
+        {
+            this.roles = new ArrayList<String>();
+        }
+        this.roles.add(role);
+    }
+        
+    public void clearUserRoles()
+    {
+        if (this.roles == null)
+        {
+            return;
+        }
+        this.roles.clear();
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private List attributeListeners = new ArrayList();
+    private HashMap attributes = new HashMap();
+    private String contextPath = null;
+    private List headers = new ArrayList();
+    private Locale locale = null;
+    private HashMap parameters = new HashMap();
+    private String pathInfo = null;
+    private Principal principal = null;
+    private String queryString = null;
+    private ServletContext servletContext = null;
+    private String servletPath = null;
+    private HttpSession session = null;
+    private String characterEncoding = null;
+    private ServletInputStream inputStream = null;
+    private List cookies = new ArrayList();
+    private Vector locales = null;
+    private String method = null;
+    private List<String> roles = null;
+
+    // ---------------------------------------------- HttpServletRequest Methods
+
+    /** {@inheritDoc} */
+    public String getAuthType()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getContextPath()
+    {
+
+        return contextPath;
+
+    }
+
+    /** {@inheritDoc} */
+    public Cookie[] getCookies()
+    {
+
+        Cookie[] array = new Cookie[cookies.size()];
+        for (int i = 0; i < cookies.size(); i++)
+        {
+            array[i] = (Cookie) cookies.get(i);
+        }
+        return array;
+    }
+
+    /** {@inheritDoc} */
+    public long getDateHeader(String name)
+    {
+
+        String match = name + ":";
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                return parseDate(header.substring(match.length() + 1).trim());
+            }
+        }
+        return (long) -1;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getHeader(String name)
+    {
+
+        String match = name + ":";
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                return header.substring(match.length() + 1).trim();
+            }
+        }
+        return null;
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getHeaderNames()
+    {
+
+        Vector values = new Vector();
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            int colon = header.indexOf(':');
+            if (colon >= 0)
+            {
+                String name = header.substring(0, colon).trim();
+                if (!values.contains(name))
+                {
+                    values.add(name);
+                }
+            }
+        }
+        return values.elements();
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getHeaders(String name)
+    {
+
+        String match = name + ":";
+        Vector values = new Vector();
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                values.add(header.substring(match.length() + 1).trim());
+            }
+        }
+        return values.elements();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getIntHeader(String name)
+    {
+
+        String match = name + ":";
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                return Integer.parseInt(header.substring(match.length() + 1)
+                        .trim());
+            }
+        }
+        return -1;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getMethod()
+    {
+
+        return method;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getPathInfo()
+    {
+
+        return pathInfo;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getPathTranslated()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getQueryString()
+    {
+
+        return queryString;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteUser()
+    {
+
+        if (principal != null)
+        {
+            return principal.getName();
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestedSessionId()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestURI()
+    {
+
+        StringBuffer sb = new StringBuffer();
+        if (contextPath != null)
+        {
+            sb.append(contextPath);
+        }
+        if (servletPath != null)
+        {
+            sb.append(servletPath);
+        }
+        if (pathInfo != null)
+        {
+            sb.append(pathInfo);
+        }
+        if (sb.length() > 0)
+        {
+            return sb.toString();
+        }
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public StringBuffer getRequestURL()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServletPath()
+    {
+
+        return (servletPath);
+
+    }
+
+    /** {@inheritDoc} */
+    public HttpSession getSession()
+    {
+
+        return getSession(true);
+
+    }
+
+    /** {@inheritDoc} */
+    public HttpSession getSession(boolean create)
+    {
+
+        if (create && (session == null))
+        {
+            this.session = new MockHttpSession(this.servletContext);
+            MockWebContainer container = getWebContainer();
+            if (container != null)
+            {
+                HttpSessionEvent se = new HttpSessionEvent(this.session);
+                container.sessionCreated(se);
+            }
+        }
+        return session;
+
+    }
+
+    /** {@inheritDoc} */
+    public Principal getUserPrincipal()
+    {
+
+        return principal;
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromCookie()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromUrl()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromURL()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdValid()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isUserInRole(String role)
+    {
+        return (this.roles != null) ? this.roles.contains(role) : false;
+    }
+
+    // ------------------------------------------------- ServletRequest Methods
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        return attributes.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        return new MockEnumeration(attributes.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public String getCharacterEncoding()
+    {
+
+        return characterEncoding;
+
+    }
+
+    /** {@inheritDoc} */
+    public int getContentLength()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getContentType()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public ServletInputStream getInputStream()
+    {
+        return this.inputStream;
+    }
+
+    public void setInputStream(MockServletInputStream stream)
+    {
+        this.inputStream = stream;
+    }
+
+    /** {@inheritDoc} */
+    public Locale getLocale()
+    {
+
+        return locale;
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getLocales()
+    {
+        if (this.locales == null)
+        {
+            locales = new Vector(Arrays.asList(Locale.getAvailableLocales()));
+        }
+        return this.locales.elements();
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalAddr()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalName()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getLocalPort()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getParameter(String name)
+    {
+
+        String[] values = (String[]) parameters.get(name);
+        if (values != null)
+        {
+            return values[0];
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public Map getParameterMap()
+    {
+
+        return parameters;
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getParameterNames()
+    {
+
+        return new MockEnumeration(parameters.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public String[] getParameterValues(String name)
+    {
+
+        return (String[]) parameters.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public String getProtocol()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public BufferedReader getReader()
+    {
+        if (this.inputStream != null)
+        {
+            try
+            {
+                Reader sourceReader = (this.characterEncoding != null) ? new InputStreamReader(
+                        this.inputStream, this.characterEncoding)
+                        : new InputStreamReader(this.inputStream);
+                return new BufferedReader(sourceReader);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String getRealPath(String path)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteAddr()
+    {
+
+        // i figure testing never assumes a specific remote - so anything works
+        return "1.2.3.4";
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteHost()
+    {
+
+        // i figure testing never assumes a specific remote - so anything works
+        return "MyfacesServer";
+
+    }
+
+    /** {@inheritDoc} */
+    public int getRemotePort()
+    {
+
+        // i figure testing never assumes a specific remote - so anything works
+        return 46123;
+
+    }
+
+    /** {@inheritDoc} */
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        return servletContext.getRequestDispatcher(path);
+    }
+
+    /** {@inheritDoc} */
+    public String getScheme()
+    {
+
+        return ("http");
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServerName()
+    {
+
+        return ("localhost");
+
+    }
+
+    /** {@inheritDoc} */
+    public int getServerPort()
+    {
+
+        return (8080);
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSecure()
+    {
+
+        return false;
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+
+        if (attributes.containsKey(name))
+        {
+            Object value = attributes.remove(name);
+            fireAttributeRemoved(name, value);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name);
+            return;
+        }
+        if (attributes.containsKey(name))
+        {
+            Object oldValue = attributes.get(name);
+            attributes.put(name, value);
+            fireAttributeReplaced(name, oldValue);
+        }
+        else
+        {
+            attributes.put(name, value);
+            fireAttributeAdded(name, value);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setCharacterEncoding(String characterEncoding)
+    {
+
+        this.characterEncoding = characterEncoding;
+
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>Fire an attribute added event to interested listeners.</p>
+     *
+     * @param key Attribute key whose value was added
+     * @param value The new attribute value
+     */
+    private void fireAttributeAdded(String key, Object value)
+    {
+        if (attributeListeners.size() < 1)
+        {
+            return;
+        }
+        ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(
+                getServletContext(), this, key, value);
+        Iterator listeners = attributeListeners.iterator();
+        while (listeners.hasNext())
+        {
+            ServletRequestAttributeListener listener = (ServletRequestAttributeListener) listeners
+                    .next();
+            listener.attributeAdded(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute removed event to interested listeners.</p>
+     *
+     * @param key Attribute key whose value was removed
+     * @param value Attribute value that was removed
+     */
+    private void fireAttributeRemoved(String key, Object value)
+    {
+        if (attributeListeners.size() < 1)
+        {
+            return;
+        }
+        ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(
+                getServletContext(), this, key, value);
+        Iterator listeners = attributeListeners.iterator();
+        while (listeners.hasNext())
+        {
+            ServletRequestAttributeListener listener = (ServletRequestAttributeListener) listeners
+                    .next();
+            listener.attributeRemoved(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute replaced event to interested listeners.</p>
+     *
+     * @param key Attribute key whose value was replaced
+     * @param value The original value
+     */
+    private void fireAttributeReplaced(String key, Object value)
+    {
+        if (attributeListeners.size() < 1)
+        {
+            return;
+        }
+        ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(
+                getServletContext(), this, key, value);
+        Iterator listeners = attributeListeners.iterator();
+        while (listeners.hasNext())
+        {
+            ServletRequestAttributeListener listener = (ServletRequestAttributeListener) listeners
+                    .next();
+            listener.attributeReplaced(event);
+        }
+    }
+
+    /**
+     * <p>The date formatting helper we will use in <code>httpTimestamp()</code>.
+     * Note that usage of this helper must be synchronized.</p>
+     */
+    private static SimpleDateFormat format = new SimpleDateFormat(
+            "EEE, dd MMM yyyy HH:mm:ss zzz");
+    static
+    {
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+    /**
+     * <p>Return a properly formatted String version of the specified
+     * date/time, formatted as required by the HTTP specification.</p>
+     *
+     * @param date Date/time, expressed as milliseconds since the epoch
+     */
+    private String formatDate(long date)
+    {
+        return format.format(new Date(date));
+    }
+
+    /**
+     * <p>Return a date/time value, parsed from the specified String.</p>
+     *
+     * @param date Date/time, expressed as a String
+     */
+    private long parseDate(String date)
+    {
+        try
+        {
+            return format.parse(date).getTime();
+        }
+        catch (ParseException e)
+        {
+            throw new IllegalArgumentException(date);
+        }
+    }
+
+    public boolean authenticate(HttpServletResponse hsr) throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public Part getPart(String string) throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void login(String string, String string1) throws ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void logout() throws ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public AsyncContext getAsyncContext()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public DispatcherType getDispatcherType()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public boolean isAsyncStarted()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public boolean isAsyncSupported()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public AsyncContext startAsync()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public AsyncContext startAsync(ServletRequest sr, ServletResponse sr1)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletResponse.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletResponse.java
new file mode 100644
index 0000000..35b1716
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpServletResponse.java
@@ -0,0 +1,474 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+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 javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>Mock implementation of <code>HttpServletResponse</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockHttpServletResponse implements HttpServletResponse
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     */
+    public MockHttpServletResponse()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Retrieve the first value that was set for the specified header,
+     * if any.  Otherwise, return <code>null</code>.</p>
+     *
+     * @param name Header name to look up
+     */
+    public String getHeader(String name)
+    {
+        String match = name + ":";
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                return header.substring(match.length() + 1).trim();
+            }
+        }
+        return null;
+    }
+
+    public Cookie getCookie(String name)
+    {
+        return (Cookie) cookies.get(name);
+    }
+    
+    public Map<String, Cookie> getCookies()
+    {
+        return cookies;
+    }
+
+    /**
+     * <p>Return the text message for the HTTP status that was set.</p>
+     */
+    public String getMessage()
+    {
+        return this.message;
+    }
+
+    /**
+     * <p>Return the HTTP status code that was set.</p>
+     */
+    public int getStatus()
+    {
+        return this.status;
+    }
+
+    /**
+     * <p>Set the <code>ServletOutputStream</code> to be returned by a call to
+     * <code>getOutputStream()</code>.</p>
+     *
+     * @param stream The <code>ServletOutputStream</code> instance to use
+     *
+     * @deprecated Let the <code>getOutputStream()</code> method create and
+     *  return an instance of <code>MockServletOutputStream</code> for you
+     */
+    public void setOutputStream(ServletOutputStream stream)
+    {
+        this.stream = stream;
+    }
+
+    /**
+     * <p>Set the <code>PrintWriter</code> to be returned by a call to
+     * <code>getWriter()</code>.</p>
+     *
+     * @param writer The <code>PrintWriter</code> instance to use
+     *
+     * @deprecated Let the <code>getWriter()</code> method create and return
+     *  an instance of <code>MockPrintWriter</code> for you
+     */
+    public void setWriter(PrintWriter writer)
+    {
+        this.writer = writer;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private String encoding = "ISO-8859-1";
+    private String contentType = "text/html";
+    private List headers = new ArrayList();
+    private String message = null;
+    private int status = HttpServletResponse.SC_OK;
+    private ServletOutputStream stream = null;
+    private PrintWriter writer = null;
+
+    private boolean committed = false;
+    private long contentLength = 0;
+    private int bufferSize = 0;
+    private Locale locale = Locale.getDefault();
+    private Map<String, Cookie> cookies = new HashMap<String, Cookie>(4);
+
+    // -------------------------------------------- HttpServletResponse Methods
+
+    /** {@inheritDoc} */
+    public void addCookie(Cookie cookie)
+    {
+        cookies.put(cookie.getName(), cookie);
+    }
+
+    /** {@inheritDoc} */
+    public void addDateHeader(String name, long value)
+    {
+
+        headers.add(name + ": " + formatDate(value));
+
+    }
+
+    /** {@inheritDoc} */
+    public void addHeader(String name, String value)
+    {
+
+        headers.add(name + ": " + value);
+
+    }
+
+    /** {@inheritDoc} */
+    public void addIntHeader(String name, int value)
+    {
+
+        headers.add(name + ": " + value);
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean containsHeader(String name)
+    {
+
+        return getHeader(name) != null;
+
+    }
+
+    /** {@inheritDoc} */
+    public String encodeRedirectUrl(String url)
+    {
+
+        return encodeRedirectURL(url);
+
+    }
+
+    /** {@inheritDoc} */
+    public String encodeRedirectURL(String url)
+    {
+
+        return url;
+
+    }
+
+    /** {@inheritDoc} */
+    public String encodeUrl(String url)
+    {
+
+        return encodeURL(url);
+
+    }
+
+    /** {@inheritDoc} */
+    public String encodeURL(String url)
+    {
+
+        return url;
+
+    }
+
+    /** {@inheritDoc} */
+    public void sendError(int status)
+    {
+        if (this.committed)
+        {
+            throw new IllegalStateException("Response is already committed");
+        }
+        this.status = status;
+        this.committed = true;
+    }
+
+    /** {@inheritDoc} */
+    public void sendError(int status, String message)
+    {
+        if (this.committed)
+        {
+            throw new IllegalStateException("Response is already committed");
+        }
+        this.status = status;
+        this.message = message;
+        this.committed = true;
+    }
+
+    /** {@inheritDoc} */
+    public void sendRedirect(String location)
+    {
+        if (this.committed)
+        {
+            throw new IllegalStateException("Response is already committed");
+        }
+        setStatus(HttpServletResponse.SC_FOUND);
+        setHeader("Location", location);
+        this.message = location;
+        this.committed = true;
+    }
+
+    /** {@inheritDoc} */
+    public void setDateHeader(String name, long value)
+    {
+
+        removeHeader(name);
+        addDateHeader(name, value);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setHeader(String name, String value)
+    {
+
+        removeHeader(name);
+        addHeader(name, value);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setIntHeader(String name, int value)
+    {
+
+        removeHeader(name);
+        addIntHeader(name, value);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setStatus(int status)
+    {
+        this.status = status;
+    }
+
+    /** {@inheritDoc} */
+    public void setStatus(int status, String message)
+    {
+        this.status = status;
+        this.message = message;
+    }
+
+    // ------------------------------------------------ ServletResponse Methods
+
+    /** {@inheritDoc} */
+    public void flushBuffer()
+    {
+
+    }
+
+    /** {@inheritDoc} */
+    public int getBufferSize()
+    {
+        return bufferSize;
+    }
+
+    /** {@inheritDoc} */
+    public String getCharacterEncoding()
+    {
+
+        return this.encoding;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getContentType()
+    {
+
+        return this.contentType;
+
+    }
+
+    /** {@inheritDoc} */
+    public Locale getLocale()
+    {
+        return this.locale;
+    }
+
+    /** {@inheritDoc} */
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+
+        if (stream == null)
+        {
+            if (writer != null)
+            {
+                throw new IllegalStateException(
+                        "Cannot call getOutputStream() after getWriter() has been called");
+            }
+            stream = new MockServletOutputStream(new ByteArrayOutputStream());
+        }
+        return stream;
+
+    }
+
+    /** {@inheritDoc} */
+    public PrintWriter getWriter() throws IOException
+    {
+
+        if (writer == null)
+        {
+            if (stream != null)
+            {
+                throw new IllegalStateException(
+                        "Cannot call getWriter() after getOutputStream() was called");
+            }
+            writer = new MockPrintWriter(new CharArrayWriter());
+        }
+        return writer;
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCommitted()
+    {
+        return committed;
+    }
+
+    /** {@inheritDoc} */
+    public void reset()
+    {
+    }
+
+    /** {@inheritDoc} */
+    public void resetBuffer()
+    {
+    }
+
+    /** {@inheritDoc} */
+    public void setBufferSize(int size)
+    {
+        this.bufferSize = size;
+    }
+
+    /** {@inheritDoc} */
+    public void setCharacterEncoding(String charset)
+    {
+
+        this.encoding = charset;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setContentLength(int length)
+    {
+        this.contentLength = length;
+    }
+
+    /** {@inheritDoc} */
+    public void setContentType(String type)
+    {
+
+        contentType = type;
+
+    }
+
+    /** {@inheritDoc} */
+    public void setLocale(Locale locale)
+    {
+        this.locale = locale;
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>The date formatting helper we will use in <code>httpTimestamp()</code>.
+     * Note that usage of this helper must be synchronized.</p>
+     */
+    private static SimpleDateFormat format = new SimpleDateFormat(
+            "EEE, dd MMM yyyy HH:mm:ss zzz");
+    static
+    {
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+    /**
+     * <p>Return a properly formatted String version of the specified
+     * date/time, formatted as required by the HTTP specification.</p>
+     *
+     * @param date Date/time, expressed as milliseconds since the epoch
+     */
+    private String formatDate(long date)
+    {
+        return format.format(new Date(date));
+    }
+
+    /**
+     * <p>Remove any header that has been set with the specific name.</p>
+     *
+     * @param name Header name to look up
+     */
+    private void removeHeader(String name)
+    {
+        String match = name + ":";
+        Iterator headers = this.headers.iterator();
+        while (headers.hasNext())
+        {
+            String header = (String) headers.next();
+            if (header.startsWith(match))
+            {
+                headers.remove();
+                return;
+            }
+        }
+    }
+
+    public Collection<String> getHeaderNames()
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public Collection<String> getHeaders(String string)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSession.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSession.java
new file mode 100644
index 0000000..f3d629b
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSession.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+/**
+ * <p>Mock implementation of <code>HttpSession</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockHttpSession implements HttpSession
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Configure a default instance.</p>
+     */
+    public MockHttpSession()
+    {
+
+        super();
+
+    }
+
+    /**
+     * <p>Configure a session instance associated with the specified
+     * servlet context.</p>
+     *
+     * @param servletContext The associated servlet context
+     */
+    public MockHttpSession(ServletContext servletContext)
+    {
+
+        super();
+        setServletContext(servletContext);
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a new listener instance that should be notified about
+     * attribute changes.</p>
+     *
+     * @param listener The new listener to be added
+     */
+    public void addAttributeListener(HttpSessionAttributeListener listener)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            attributeListeners.add(listener);
+        }
+        else
+        {
+            container.subscribeListener(listener);
+        }
+    }
+
+    /**
+     * <p>Set the ServletContext associated with this session.</p>
+     *
+     * @param servletContext The associated servlet context
+     */
+    public void setServletContext(ServletContext servletContext)
+    {
+
+        this.servletContext = servletContext;
+
+    }
+
+    protected MockWebContainer getWebContainer()
+    {
+        if (this.servletContext instanceof MockServletContext)
+        {
+            return ((MockServletContext)this.servletContext).getWebContainer();
+        }
+        return null;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private List attributeListeners = new ArrayList();
+    private HashMap attributes = new HashMap();
+    private String id = "123";
+    private ServletContext servletContext = null;
+    private boolean invalid = false;
+    private MockWebContainer webContainer;
+
+    // ---------------------------------------------------------- Public Methods
+
+    /**
+     * <p>Set the session identifier of this session.</p>
+     *
+     * @param id The new session identifier
+     */
+    public void setId(String id)
+    {
+        this.id = id;
+    }
+
+    // ----------------------------------------------------- HttpSession Methods
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        assertValidity();
+
+        return attributes.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        assertValidity();
+
+        return new MockEnumeration(attributes.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public long getCreationTime()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getId()
+    {
+
+        return this.id;
+
+    }
+
+    /** {@inheritDoc} */
+    public long getLastAccessedTime()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxInactiveInterval()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public ServletContext getServletContext()
+    {
+
+        return this.servletContext;
+
+    }
+
+    /** {@inheritDoc} */
+    public HttpSessionContext getSessionContext()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Object getValue(String name)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String[] getValueNames()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void invalidate()
+    {
+
+        assertValidity();
+
+        attributes.clear();
+        invalid = true;
+        
+        MockWebContainer container = getWebContainer();
+        if (container != null)
+        {
+            HttpSessionEvent se = new HttpSessionEvent(this);
+            container.sessionDestroyed(se);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNew()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void putValue(String name, Object value)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+
+        assertValidity();
+
+        if (attributes.containsKey(name))
+        {
+            Object value = attributes.remove(name);
+            fireAttributeRemoved(name, value);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeValue(String name)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+
+        assertValidity();
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name);
+            return;
+        }
+        if (attributes.containsKey(name))
+        {
+            Object oldValue = attributes.get(name);
+            attributes.put(name, value);
+            fireAttributeReplaced(name, oldValue, value);
+        }
+        else
+        {
+            attributes.put(name, value);
+            fireAttributeAdded(name, value);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxInactiveInterval(int interval)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    // --------------------------------------------------------- Support Methods
+
+    /**
+     * <p>Fire an attribute added event to interested listeners.</p>
+     *
+     * @param key Attribute whose value was added
+     * @param value The new value
+     */
+    private void fireAttributeAdded(String key, Object value)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueBound(event);
+            }
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners
+                        .next();
+                listener.attributeAdded(event);
+            }
+        }
+        else
+        {
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueBound(event);
+            }            
+            container.attributeAdded(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute removed event to interested listeners.</p>
+     *
+     * @param key Attribute whose value was removed
+     * @param value The removed value
+     */
+    private void fireAttributeRemoved(String key, Object value)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueUnbound(event);
+            }            
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners
+                        .next();
+                listener.attributeRemoved(event);
+            }
+        }
+        else
+        {
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueUnbound(event);
+            }
+            container.attributeRemoved(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute replaced event to interested listeners.</p>
+     *
+     * @param key Attribute whose value was replaced
+     * @param value The original value
+     */
+    private void fireAttributeReplaced(String key, Object oldValue, Object value)
+    {
+        if (oldValue instanceof HttpSessionBindingListener)
+        {
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    oldValue);
+            ((HttpSessionBindingListener)value).valueUnbound(event);
+        }
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {        
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueBound(event);
+            }            
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners
+                        .next();
+                listener.attributeReplaced(event);
+            }
+        }
+        else
+        {
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key,
+                    value);
+            if (value instanceof HttpSessionBindingListener)
+            {
+                ((HttpSessionBindingListener)value).valueBound(event);
+            }
+            container.attributeReplaced(event);
+        }
+    }
+
+    /**
+     * <p>Throws an {@link IllegalStateException} if this session is invalid.</p>
+     */
+    private void assertValidity()
+    {
+        if (invalid)
+        {
+            throw new IllegalStateException("Session is invalid.");
+        }
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSessionProxy.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSessionProxy.java
new file mode 100644
index 0000000..181da0d
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockHttpSessionProxy.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.myfaces.test.mock;
+
+import java.util.Enumeration;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+/**
+ * Proxy used to trigger session creation when it is accessed
+ */
+public class MockHttpSessionProxy extends MockHttpSession
+{
+    private MockServletContext servletContext;
+    private MockHttpServletRequest request;
+    private MockHttpSession delegate;
+
+    public MockHttpSessionProxy(MockServletContext servletContext,
+        MockHttpServletRequest request)
+    {
+        super(servletContext);
+        this.servletContext = servletContext;
+    }
+    
+    @Override
+    public void addAttributeListener(HttpSessionAttributeListener listener)
+    {
+        getWrapped().addAttributeListener(listener);
+    }
+
+    @Override
+    public void setServletContext(ServletContext servletContext)
+    {
+        if (servletContext instanceof MockServletContext)
+        {
+            this.servletContext = (MockServletContext) servletContext;
+        }
+        getWrapped().setServletContext(servletContext);
+    }
+
+    @Override
+    public void setId(String id)
+    {
+        getWrapped().setId(id);
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        return getWrapped().getAttribute(name);
+    }
+
+    @Override
+    public Enumeration getAttributeNames()
+    {
+        return getWrapped().getAttributeNames();
+    }
+
+    @Override
+    public long getCreationTime()
+    {
+        return getWrapped().getCreationTime();
+    }
+
+    @Override
+    public String getId()
+    {
+        return getWrapped().getId();
+    }
+
+    @Override
+    public long getLastAccessedTime()
+    {
+        return getWrapped().getLastAccessedTime();
+    }
+
+    @Override
+    public int getMaxInactiveInterval()
+    {
+        return getWrapped().getMaxInactiveInterval();
+    }
+
+    @Override
+    public ServletContext getServletContext()
+    {
+        return servletContext == null ? getWrapped().getServletContext() : servletContext;
+    }
+
+    @Override
+    public HttpSessionContext getSessionContext()
+    {
+        return getWrapped().getSessionContext();
+    }
+
+    @Override
+    public Object getValue(String name)
+    {
+        return getWrapped().getValue(name);
+    }
+
+    @Override
+    public String[] getValueNames()
+    {
+        return getWrapped().getValueNames();
+    }
+
+    @Override
+    public void invalidate()
+    {
+        getWrapped().invalidate();
+    }
+
+    @Override
+    public boolean isNew()
+    {
+        return getWrapped().isNew();
+    }
+
+    @Override
+    public void putValue(String name, Object value)
+    {
+        getWrapped().putValue(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name)
+    {
+        getWrapped().removeAttribute(name);
+    }
+
+    @Override
+    public void removeValue(String name)
+    {
+        getWrapped().removeValue(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        getWrapped().setAttribute(name, value);
+    }
+
+    @Override
+    public void setMaxInactiveInterval(int interval)
+    {
+        getWrapped().setMaxInactiveInterval(interval);
+    }
+    
+    public MockHttpSession getWrapped()
+    {
+        if (delegate == null)
+        {
+            if (request != null)
+            {
+                delegate = (MockHttpSession) request.getSession(true);
+            }
+            else
+            {
+                delegate = new MockHttpSession(this.servletContext);
+                MockWebContainer container = getWebContainer();
+                if (container != null)
+                {
+                    HttpSessionEvent se = new HttpSessionEvent(delegate);
+                    container.sessionCreated(se);
+                }
+            }
+        }
+        return delegate;
+    }
+
+    /**
+     * @return the request
+     */
+    public MockHttpServletRequest getRequest()
+    {
+        return request;
+    }
+
+    /**
+     * @param request the request to set
+     */
+    public void setRequest(MockHttpServletRequest request)
+    {
+        this.request = request;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockNavigationHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockNavigationHandler.java
new file mode 100644
index 0000000..07c3567
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockNavigationHandler.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.myfaces.test.mock;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.faces.application.NavigationHandler;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>NavigationHandler</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockNavigationHandler extends NavigationHandler
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockNavigationHandler()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a outcome-viewId pair to the destinations map.</p>
+     *
+     * @param outcome Logical outcome string
+     * @param viewId Destination view identifier
+     */
+    public void addDestination(String outcome, String viewId)
+    {
+
+        destinations.put(outcome, viewId);
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>Set of destination view ids, keyed by logical outcome String
+     * that will cause navigation to that view id.</p>
+     */
+    private Map destinations = new HashMap();
+
+    // ----------------------------------------------- NavigationHandler Methods
+
+    /**
+     * <p>Process the specified navigation request.</p>
+     *
+     * @param context <code>FacesContext</code> for the current request
+     * @param action Action method being executed
+     * @param outcome Logical outcome from this action method
+     */
+    public void handleNavigation(FacesContext context, String action,
+            String outcome)
+    {
+
+        // Navigate solely based on outcome, if we get a match
+        String viewId = (String) destinations.get(outcome);
+        if (viewId != null)
+        {
+            UIViewRoot view = getViewHandler(context).createView(context,
+                    viewId);
+            context.setViewRoot(view);
+        }
+
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * <p>Return the <code>ViewHandler</code> instance for this application.</p>
+     *
+     * @param context <code>FacesContext</code> for the current request
+     */
+    private ViewHandler getViewHandler(FacesContext context)
+    {
+
+        return context.getApplication().getViewHandler();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.java
new file mode 100644
index 0000000..7526526
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.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.myfaces.test.mock;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.PartialResponseWriter;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.event.PhaseId;
+
+import org.apache.myfaces.test.mock.visit.MockVisitCallback;
+
+/**
+ * <p>Mock implementation of <code>PartialViewContext</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockPartialViewContext extends PartialViewContext
+{
+
+    private static final String FACES_REQUEST = "Faces-Request";
+    private static final String PARTIAL_AJAX = "partial/ajax";
+    private static final String PARTIAL_PROCESS = "partial/process";
+    private static final String SOURCE_PARAM_NAME = "javax.faces.source";
+    private FacesContext _facesContext = null;
+    private Boolean _ajaxRequest = null;
+    private Collection<String> _executeClientIds = null;
+    private Collection<String> _renderClientIds = null;
+    private Boolean _partialRequest = null;
+    private Boolean _renderAll = null;
+    private PartialResponseWriter _partialResponseWriter = null;
+    private List<String> _evalScripts = new ArrayList<String>();
+
+    public MockPartialViewContext(FacesContext context)
+    {
+        _facesContext = context;
+    }
+
+    @Override
+    public boolean isAjaxRequest()
+    {
+        if (_ajaxRequest == null)
+        {
+            String requestType = _facesContext.getExternalContext()
+                    .getRequestHeaderMap().get(FACES_REQUEST);
+            _ajaxRequest = (requestType != null && PARTIAL_AJAX
+                    .equals(requestType));
+        }
+        return _ajaxRequest;
+    }
+
+    @Override
+    public boolean isExecuteAll()
+    {
+
+        if (isAjaxRequest())
+        {
+            String executeMode = _facesContext.getExternalContext()
+                    .getRequestParameterMap().get(
+                            PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
+            if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS
+                    .equals(executeMode))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isPartialRequest()
+    {
+
+        if (_partialRequest == null)
+        {
+            String requestType = _facesContext.getExternalContext()
+                    .getRequestHeaderMap().get(FACES_REQUEST);
+            _partialRequest = (requestType != null && PARTIAL_PROCESS
+                    .equals(requestType));
+        }
+        return isAjaxRequest() || _partialRequest;
+    }
+
+    @Override
+    public boolean isRenderAll()
+    {
+
+        if (_renderAll == null)
+        {
+            if (isAjaxRequest())
+            {
+                String executeMode = _facesContext.getExternalContext()
+                        .getRequestParameterMap().get(
+                                PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
+                if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS
+                        .equals(executeMode))
+                {
+                    _renderAll = true;
+                }
+            }
+            if (_renderAll == null)
+            {
+                _renderAll = false;
+            }
+        }
+        return _renderAll;
+    }
+
+    @Override
+    public void setPartialRequest(boolean isPartialRequest)
+    {
+
+        _partialRequest = isPartialRequest;
+
+    }
+
+    @Override
+    public void setRenderAll(boolean renderAll)
+    {
+
+        _renderAll = renderAll;
+    }
+
+    @Override
+    public Collection<String> getExecuteIds()
+    {
+
+        if (_executeClientIds == null)
+        {
+            String executeMode = _facesContext.getExternalContext()
+                    .getRequestParameterMap().get(
+                            PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
+
+            if (executeMode != null
+                    && !"".equals(executeMode)
+                    &&
+                    //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode) &&
+                    !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS
+                            .equals(executeMode))
+            {
+
+                String[] clientIds = splitShortString(
+                        _replaceTabOrEnterCharactersWithSpaces(executeMode),
+                        ' ');
+
+                //The collection must be mutable
+                List<String> tempList = new ArrayList<String>();
+                for (String clientId : clientIds)
+                {
+                    if (clientId.length() > 0)
+                    {
+                        tempList.add(clientId);
+                    }
+                }
+                // The "javax.faces.source" parameter needs to be added to the list of
+                // execute ids if missing (otherwise, we'd never execute an action associated
+                // with, e.g., a button).
+
+                String source = _facesContext.getExternalContext()
+                        .getRequestParameterMap().get(SOURCE_PARAM_NAME);
+
+                if (source != null)
+                {
+                    source = source.trim();
+
+                    if (!tempList.contains(source))
+                    {
+                        tempList.add(source);
+                    }
+                }
+
+                _executeClientIds = tempList;
+            }
+            else
+            {
+                _executeClientIds = new ArrayList<String>();
+            }
+        }
+        return _executeClientIds;
+    }
+
+    @Override
+    public Collection<String> getRenderIds()
+    {
+
+        if (_renderClientIds == null)
+        {
+            String renderMode = _facesContext.getExternalContext()
+                    .getRequestParameterMap().get(
+                            PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
+
+            if (renderMode != null
+                    && !"".equals(renderMode)
+                    &&
+                    //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode) &&
+                    !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS
+                            .equals(renderMode))
+            {
+                String[] clientIds = splitShortString(
+                        _replaceTabOrEnterCharactersWithSpaces(renderMode), ' ');
+
+                //The collection must be mutable
+                List<String> tempList = new ArrayList<String>();
+                for (String clientId : clientIds)
+                {
+                    if (clientId.length() > 0)
+                    {
+                        tempList.add(clientId);
+                    }
+                }
+                _renderClientIds = tempList;
+            }
+            else
+            {
+                _renderClientIds = new ArrayList<String>();
+
+                if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS
+                        .equals(renderMode))
+                {
+                    _renderClientIds
+                            .add(PartialResponseWriter.RENDER_ALL_MARKER);
+                }
+            }
+        }
+        return _renderClientIds;
+    }
+
+    @Override
+    public PartialResponseWriter getPartialResponseWriter()
+    {
+        if (_partialResponseWriter == null)
+        {
+            ResponseWriter responseWriter = _facesContext.getResponseWriter();
+            if (responseWriter == null)
+            {
+                // This case happens when getPartialResponseWriter() is called before
+                // render phase, like in ExternalContext.redirect(). We have to create a
+                // ResponseWriter from the RenderKit and then wrap if necessary. 
+                try
+                {
+                    responseWriter = _facesContext.getRenderKit()
+                            .createResponseWriter(
+                                    _facesContext.getExternalContext()
+                                            .getResponseOutputWriter(),
+                                    "text/xml",
+                                    _facesContext.getExternalContext()
+                                            .getRequestCharacterEncoding());
+                }
+                catch (IOException e)
+                {
+                    throw new IllegalStateException(
+                            "Cannot create Partial Response Writer", e);
+                }
+            }
+            // It is possible that the RenderKit return a PartialResponseWriter instance when 
+            // createResponseWriter,  so we should cast here for it and prevent double wrapping.
+            if (responseWriter instanceof PartialResponseWriter)
+            {
+                _partialResponseWriter = (PartialResponseWriter) responseWriter;
+            }
+            else
+            {
+                _partialResponseWriter = new PartialResponseWriter(
+                        responseWriter);
+            }
+        }
+        return _partialResponseWriter;
+    }
+
+    @Override
+    public void processPartial(PhaseId phaseId)
+    {
+
+        UIViewRoot viewRoot = _facesContext.getViewRoot();
+
+        VisitContext visitCtx = VisitContext.createVisitContext(_facesContext,
+                null, null);
+        viewRoot.visitTree(visitCtx, new MockVisitCallback());
+    }
+
+    @Override
+    public void release()
+    {
+        _executeClientIds = null;
+        _renderClientIds = null;
+        _ajaxRequest = null;
+        _partialRequest = null;
+        _renderAll = null;
+        _facesContext = null;
+        _evalScripts = new ArrayList<String>();
+    }
+    
+    public List<String> getEvalScripts()
+    {
+        return _evalScripts;
+    }
+
+    private static String[] splitShortString(String str, char separator)
+    {
+        int len = str.length();
+
+        int lastTokenIndex = 0;
+
+        // Step 1: how many substrings?
+        //      We exchange double scan time for less memory allocation
+        for (int pos = str.indexOf(separator); pos >= 0; pos = str.indexOf(
+                separator, pos + 1))
+        {
+            lastTokenIndex++;
+        }
+
+        // Step 2: allocate exact size array
+        String[] list = new String[lastTokenIndex + 1];
+
+        int oldPos = 0;
+
+        // Step 3: retrieve substrings
+        int pos = str.indexOf(separator);
+        int i = 0;
+        
+        while (pos >= 0)
+        {
+            list[i++] = str.substring(oldPos, pos);
+            oldPos = (pos + 1);
+            pos = str.indexOf(separator, oldPos);
+        }
+
+        list[lastTokenIndex] = str.substring(oldPos, len);
+
+        return list;
+    }
+
+    private String _replaceTabOrEnterCharactersWithSpaces(String mode)
+    {
+        StringBuilder builder = new StringBuilder(mode.length());
+        for (int i = 0; i < mode.length(); i++)
+        {
+            if (mode.charAt(i) == '\t' || mode.charAt(i) == '\n')
+            {
+                builder.append(' ');
+            }
+            else
+            {
+                builder.append(mode.charAt(i));
+            }
+        }
+        return builder.toString();
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.java
new file mode 100644
index 0000000..35fcdc8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.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.myfaces.test.mock;
+
+import javax.faces.context.FacesContext;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.PartialViewContextFactory;
+
+/**
+ * <p>Mock implementation of <code>PartialViewContextFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockPartialViewContextFactory extends PartialViewContextFactory
+{
+
+    @Override
+    public PartialViewContext getPartialViewContext(FacesContext context)
+    {
+        return new MockPartialViewContext(context);
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletContext.java
new file mode 100644
index 0000000..578b225
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletContext.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequestDispatcher;
+
+/**
+ * <p>Mock implementation of <code>PortletContext</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockPortletContext implements PortletContext
+{
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a context initialization parameter to the set of parameters
+     * recognized by this instance.</p>
+     *
+     * @param name Parameter name
+     * @param value Parameter value
+     */
+    public void addInitParameter(String name, String value)
+    {
+
+        parameters.put(name, value);
+
+    }
+
+    /**
+     * <p>Add a new MIME type mapping to the set of mappings recognized by this
+     * instance.</p>
+     * 
+     * @param extension Extension to check for (without the period)
+     * @param contentType Corresponding content type
+     */
+    public void addMimeType(String extension, String contentType)
+    {
+
+        mimeTypes.put(extension, contentType);
+
+    }
+
+    /**
+     * <p>Set the document root for <code>getRealPath()</code> resolution.
+     * This parameter <strong>MUST</strong> represent a directory.</p>
+     *
+     * @param documentRoot The new base directory
+     */
+    public void setDocumentRoot(File documentRoot)
+    {
+
+        this.documentRoot = documentRoot;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private Hashtable attributes = new Hashtable();
+    private File documentRoot = null;
+    private Hashtable mimeTypes = new Hashtable();
+    private Hashtable parameters = new Hashtable();
+
+    // -------------------------------------------------- PortletContext Methods
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        return attributes.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        return attributes.keys();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getInitParameter(String name)
+    {
+
+        return (String) parameters.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getInitParameterNames()
+    {
+
+        return parameters.keys();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getMajorVersion()
+    {
+
+        return 1;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getMimeType(String path)
+    {
+
+        int period = path.lastIndexOf('.');
+        if (period < 0)
+        {
+            return null;
+        }
+        String extension = path.substring(period + 1);
+        return (String) mimeTypes.get(extension);
+
+    }
+
+    public int getMinorVersion()
+    {
+
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    public PortletRequestDispatcher getNamedDispatcher(String arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getPortletContextName()
+    {
+
+        return "MockPortletContext";
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRealPath(String path)
+    {
+
+        if (documentRoot != null)
+        {
+            if (!path.startsWith("/"))
+            {
+                throw new IllegalArgumentException("The specified path ('"
+                        + path + "') does not start with a '/' character");
+            }
+            File resolved = new File(documentRoot, path.substring(1));
+            try
+            {
+                return resolved.getCanonicalPath();
+            }
+            catch (IOException e)
+            {
+                return resolved.getAbsolutePath();
+            }
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletRequestDispatcher getRequestDispatcher(String arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public URL getResource(String path) throws MalformedURLException
+    {
+
+        if (documentRoot != null)
+        {
+            if (!path.startsWith("/"))
+            {
+                throw new MalformedURLException("The specified path ('" + path
+                        + "') does not start with a '/' character");
+            }
+            File resolved = new File(documentRoot, path.substring(1));
+            if (resolved.exists())
+            {
+                return resolved.toURL();
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public InputStream getResourceAsStream(String path)
+    {
+
+        try
+        {
+            URL url = getResource(path);
+            if (url != null)
+            {
+                return url.openStream();
+            }
+        }
+        catch (Exception e)
+        {
+        }
+        return null;
+
+    }
+
+    /** {@inheritDoc} */
+    public Set getResourcePaths(String path)
+    {
+
+        if (documentRoot == null)
+        {
+            return null;
+        }
+
+        // Enforce the leading slash restriction
+        if (!path.startsWith("/"))
+        {
+            throw new IllegalArgumentException("The specified path ('" + path
+                    + "') does not start with a '/' character");
+        }
+
+        // Locate the File node for this path's directory (if it exists)
+        File node = new File(documentRoot, path.substring(1));
+        if (!node.exists())
+        {
+            return null;
+        }
+        if (!node.isDirectory())
+        {
+            return null;
+        }
+
+        // Construct a Set containing the paths to the contents of this
+        // directory
+        Set set = new HashSet();
+        String[] files = node.list();
+        if (files == null)
+        {
+            return null;
+        }
+        for (int i = 0; i < files.length; i++)
+        {
+            String subfile = path + files[i];
+            File subnode = new File(node, files[i]);
+            if (subnode.isDirectory())
+            {
+                subfile += "/";
+            }
+            set.add(subfile);
+        }
+
+        // Return the completed set
+        return set;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServerInfo()
+    {
+
+        return "MockPortletContext";
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message)
+    {
+
+        System.out.println(message);
+
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message, Throwable exception)
+    {
+
+        System.out.println(message);
+        exception.printStackTrace();
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+
+        if (attributes.containsKey(name))
+        {
+            attributes.remove(name);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name);
+            return;
+        }
+        attributes.put(name, value);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletRequest.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletRequest.java
new file mode 100644
index 0000000..86815a9
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletRequest.java
@@ -0,0 +1,436 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.WindowState;
+
+/**
+ * <p> Mock implementation of <code>PortletRequest</code>. </p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockPortletRequest implements PortletRequest
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockPortletRequest()
+    {
+
+        super();
+
+    }
+
+    public MockPortletRequest(PortletSession session)
+    {
+
+        super();
+        this.session = session;
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p> Add a request parameter for this request. </p>
+     *
+     * @param name Parameter name
+     * @param value Parameter value
+     */
+    public void addParameter(String name, String value)
+    {
+
+        String[] values = (String[]) parameters.get(name);
+        if (values == null)
+        {
+            String[] results = new String[] { value };
+            parameters.put(name, results);
+            return;
+        }
+        String[] results = new String[values.length + 1];
+        System.arraycopy(values, 0, results, 0, values.length);
+        results[values.length] = value;
+        parameters.put(name, results);
+
+    }
+
+    /**
+     * <p> Set the <code>PortletSession</code> associated with this request.
+     * </p>
+     *
+     * @param session The new session
+     */
+    public void setPortletSession(PortletSession session)
+    {
+
+        this.session = session;
+    }
+
+    /**
+     * <p> Set the <code>Locale</code> associated with this request. </p>
+     *
+     * @param locale The new locale
+     */
+    public void setLocale(Locale locale)
+    {
+
+        this.locale = locale;
+
+    }
+
+    /**
+     * <p> Set the <code>Principal</code> associated with this request. </p>
+     *
+     * @param principal The new Principal
+     */
+    public void setUserPrincipal(Principal principal)
+    {
+
+        this.principal = principal;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private Map attributes = new HashMap();
+    private String contextPath = null;
+    private Locale locale = null;
+    private Map parameters = new HashMap();
+    private Principal principal = null;
+    private PortletSession session = null;
+
+    // -------------------------------------------------- PortletRequest Methods
+
+    /** {@inheritDoc} */
+    public String getAuthType()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getContextPath()
+    {
+
+        return contextPath;
+
+    }
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        return attributes.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        return new MockEnumeration(attributes.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public Locale getLocale()
+    {
+
+        return locale;
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getLocales()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getParameter(String name)
+    {
+
+        String[] values = (String[]) parameters.get(name);
+        if (values != null)
+        {
+            return values[0];
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public Map getParameterMap()
+    {
+
+        return parameters;
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getParameterNames()
+    {
+
+        return new MockEnumeration(parameters.keySet().iterator());
+
+    }
+
+    /** {@inheritDoc} */
+    public String[] getParameterValues(String name)
+    {
+
+        return (String[]) parameters.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public PortalContext getPortalContext()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletMode getPortletMode()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletSession getPortletSession()
+    {
+
+        return getPortletSession(true);
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletSession getPortletSession(boolean create)
+    {
+
+        if (create && (session == null))
+        {
+            throw new UnsupportedOperationException();
+        }
+        return session;
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletPreferences getPreferences()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getProperties(String arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getProperty(String arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getPropertyNames()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteUser()
+    {
+
+        if (principal != null)
+        {
+            return principal.getName();
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestedSessionId()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getResponseContentType()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getResponseContentTypes()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getScheme()
+    {
+
+        return ("http");
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServerName()
+    {
+
+        return ("localhost");
+
+    }
+
+    /** {@inheritDoc} */
+    public int getServerPort()
+    {
+
+        return (8080);
+
+    }
+
+    /** {@inheritDoc} */
+    public Principal getUserPrincipal()
+    {
+
+        return principal;
+
+    }
+
+    /** {@inheritDoc} */
+    public WindowState getWindowState()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isPortletModeAllowed(PortletMode arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdValid()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSecure()
+    {
+
+        return false;
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isUserInRole(String arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isWindowStateAllowed(WindowState arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+
+        if (attributes.containsKey(name))
+        {
+            attributes.remove(name);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name);
+            return;
+        }
+        attributes.put(name, value);
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletResponse.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletResponse.java
new file mode 100644
index 0000000..33fa0f0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletResponse.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.myfaces.test.mock;
+
+import javax.portlet.PortletResponse;
+
+/**
+ * <p>Mock implementation of <code>PortletResponse</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockPortletResponse implements PortletResponse
+{
+
+    /**
+     * <p>Return a default instance.</p>
+     */
+    public MockPortletResponse()
+    {
+
+    }
+
+    // -------------------------------------------------- PortletContext Methods
+
+    /** {@inheritDoc} */
+    public void addProperty(String name, String value)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String encodeURL(String url)
+    {
+
+        return url;
+    }
+
+    /** {@inheritDoc} */
+    public void setProperty(String name, String value)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletSession.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletSession.java
new file mode 100644
index 0000000..64069c0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPortletSession.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 org.apache.myfaces.test.mock;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.PortletContext;
+import javax.portlet.PortletSession;
+
+/**
+ * <p> Mock implementation of <code>PortletSession</code>. </p>
+ * 
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockPortletSession implements PortletSession
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p> Configure a default instance. </p>
+     */
+    public MockPortletSession()
+    {
+
+        super();
+
+    }
+
+    /**
+     * <p> Configure a session instance associated with the specified servlet
+     * context. </p>
+     *
+     * @param servletContext The associated servlet context
+     */
+    public MockPortletSession(PortletContext portletContext)
+    {
+
+        super();
+        this.portletContext = portletContext;
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p> Set the <code>PortletContext</code> associated with this session.
+     * </p>
+     *
+     * @param servletContext The associated servlet context
+     */
+    public void setPortletContext(PortletContext portletContext)
+    {
+
+        this.portletContext = portletContext;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private Map portletAttributes = new HashMap();
+    private Map applicationAttributes = new HashMap();
+    private String id = "123";
+    private PortletContext portletContext = null;
+
+    // ---------------------------------------------------------- Public Methods
+
+    /**
+     * <p> Set the session identifier of this session. </p>
+     *
+     * @param id The new session identifier
+     */
+    public void setId(String id)
+    {
+
+        this.id = id;
+
+    }
+
+    // -------------------------------------------------- PortletSession Methods
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        return getAttribute(name, PORTLET_SCOPE);
+
+    }
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name, int scope)
+    {
+
+        if (scope == PORTLET_SCOPE)
+        {
+            return portletAttributes.get(name);
+        }
+        else if (scope == APPLICATION_SCOPE)
+        {
+            return applicationAttributes.get(name);
+        }
+
+        throw new IllegalArgumentException("Scope constant " + scope
+                + " not recognized");
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        return getAttributeNames(PORTLET_SCOPE);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames(int scope)
+    {
+
+        if (scope == PORTLET_SCOPE)
+        {
+            return new MockEnumeration(portletAttributes.keySet().iterator());
+        }
+        else if (scope == APPLICATION_SCOPE)
+        {
+            return new MockEnumeration(applicationAttributes.keySet()
+                    .iterator());
+        }
+
+        throw new IllegalArgumentException("Scope constant " + scope
+                + " not recognized");
+
+    }
+
+    /** {@inheritDoc} */
+    public long getCreationTime()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getId()
+    {
+
+        return this.id;
+
+    }
+
+    /** {@inheritDoc} */
+    public long getLastAccessedTime()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxInactiveInterval()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public PortletContext getPortletContext()
+    {
+
+        return portletContext;
+    }
+
+    /** {@inheritDoc} */
+    public void invalidate()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNew()
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+
+        removeAttribute(name, PORTLET_SCOPE);
+
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name, int scope)
+    {
+
+        Map attributes;
+        if (scope == PORTLET_SCOPE)
+        {
+            attributes = portletAttributes;
+        }
+        else if (scope == APPLICATION_SCOPE)
+        {
+            attributes = applicationAttributes;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Scope constant " + scope
+                    + " not recognized");
+        }
+        if (attributes.containsKey(name))
+        {
+            attributes.remove(name);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+
+        setAttribute(name, value, PORTLET_SCOPE);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value, int scope)
+    {
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name, scope);
+            return;
+        }
+
+        Map attributes;
+        if (scope == PORTLET_SCOPE)
+        {
+            attributes = portletAttributes;
+        }
+        else if (scope == APPLICATION_SCOPE)
+        {
+            attributes = applicationAttributes;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Scope constant " + scope
+                    + " not recognized");
+        }
+        attributes.put(name, value);
+
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxInactiveInterval(int arg0)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPrincipal.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPrincipal.java
new file mode 100644
index 0000000..2462815
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPrincipal.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.myfaces.test.mock;
+
+import java.security.Principal;
+
+/**
+ * <p>Mock implementation of <code>Principal</code>.</p>
+ * 
+ * @since 1.0.0
+ */
+public class MockPrincipal implements Principal
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default Principal instance.</p>
+     */
+    public MockPrincipal()
+    {
+        this(null);
+    }
+
+    /**
+     * <p>Construct a Principal with the specified name.</p>
+     *
+     * @param name Name for this Principal
+     */
+    public MockPrincipal(String name)
+    {
+        this.name = name;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The name for this Principal intance.</p>
+     */
+    private String name = null;
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Set the name for this Principal.</p>
+     *
+     * @param name The new name
+     */
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    // ------------------------------------------------------- Principal Methods
+
+    /** {@inheritDoc} */
+    public String getName()
+    {
+        return this.name;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockPrintWriter.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockPrintWriter.java
new file mode 100644
index 0000000..a98e170
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockPrintWriter.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.myfaces.test.mock;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+
+/**
+ * <p>Mock implementation of <code>PrintWriter</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockPrintWriter extends PrintWriter
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     *
+     * @param writer Temporary buffer storage for us to use
+     */
+    public MockPrintWriter(CharArrayWriter writer)
+    {
+        super(writer);
+        this.caw = writer;
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Return the content that has been written to this writer.</p>
+     */
+    public char[] content()
+    {
+        return caw.toCharArray();
+    }
+
+    /**
+     * <p>Reset this output stream so that it appears no content has been
+     * written.</p>
+     */
+    public void reset()
+    {
+        caw.reset();
+    }
+
+    /**
+     * <p>Return the number of characters that have been written to this writer.</p>
+     */
+    public int size()
+    {
+        return caw.size();
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The writer we will use for buffering.</p>
+     */
+    private CharArrayWriter caw = null;
+
+    // ----------------------------------------------------- PrintWriter Methods
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKit.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKit.java
new file mode 100644
index 0000000..3de55f8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKit.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.myfaces.test.mock;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.faces.context.ResponseStream;
+import javax.faces.context.ResponseWriter;
+import javax.faces.render.RenderKit;
+import javax.faces.render.Renderer;
+import javax.faces.render.ResponseStateManager;
+
+/**
+ * <p>Mock implementation of <code>RenderKit</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockRenderKit extends RenderKit
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     */
+    public MockRenderKit()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    public void setResponseStateManager(ResponseStateManager rsm)
+    {
+        this.rsm = rsm;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The set of renderers registered here.</p>
+     */
+    private Map renderers = new HashMap();
+    private ResponseStateManager rsm = new MockResponseStateManager();
+
+    // ------------------------------------------------------- RenderKit Methods
+
+    /** {@inheritDoc} */
+    public void addRenderer(String family, String rendererType,
+            Renderer renderer)
+    {
+
+        if ((family == null) || (rendererType == null) || (renderer == null))
+        {
+            throw new NullPointerException();
+        }
+        renderers.put(family + "|" + rendererType, renderer);
+
+    }
+
+    /** {@inheritDoc} */
+    public Renderer getRenderer(String family, String rendererType)
+    {
+
+        if ((family == null) || (rendererType == null))
+        {
+            throw new NullPointerException();
+        }
+        return (Renderer) renderers.get(family + "|" + rendererType);
+
+    }
+
+    /** {@inheritDoc} */
+    public ResponseWriter createResponseWriter(Writer writer,
+            String contentTypeList, String characterEncoding)
+    {
+
+        return new MockResponseWriter(writer, contentTypeList,
+                characterEncoding);
+
+    }
+
+    /** {@inheritDoc} */
+    public ResponseStream createResponseStream(OutputStream out)
+    {
+
+        final OutputStream stream = out;
+        return new ResponseStream()
+        {
+
+            public void close() throws IOException
+            {
+                stream.close();
+            }
+
+            public void flush() throws IOException
+            {
+                stream.flush();
+            }
+
+            public void write(byte[] b) throws IOException
+            {
+                stream.write(b);
+            }
+
+            public void write(byte[] b, int off, int len) throws IOException
+            {
+                stream.write(b, off, len);
+            }
+
+            public void write(int b) throws IOException
+            {
+                stream.write(b);
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    public ResponseStateManager getResponseStateManager()
+    {
+        return rsm;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKitFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKitFactory.java
new file mode 100644
index 0000000..367ecb6
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockRenderKitFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.faces.context.FacesContext;
+import javax.faces.render.RenderKit;
+import javax.faces.render.RenderKitFactory;
+
+/**
+ * <p>Mock implementation of <code>RenderKitFactory</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockRenderKitFactory extends RenderKitFactory
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     */
+    public MockRenderKitFactory()
+    {
+
+        renderKits = new HashMap();
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The set of render kits that have been registered here.</p>
+     */
+    private Map renderKits = new HashMap();
+
+    // ------------------------------------------------ RenderKitFactory Methods
+
+    /** {@inheritDoc} */
+    public void addRenderKit(String renderKitId, RenderKit renderKit)
+    {
+
+        if ((renderKitId == null) || (renderKit == null))
+        {
+            throw new NullPointerException();
+        }
+        if (renderKits.containsKey(renderKitId))
+        {
+            throw new IllegalArgumentException(renderKitId);
+        }
+        renderKits.put(renderKitId, renderKit);
+
+    }
+
+    /** {@inheritDoc} */
+    public RenderKit getRenderKit(FacesContext context, String renderKitId)
+    {
+
+        if (renderKitId == null)
+        {
+            throw new NullPointerException();
+        }
+        RenderKit renderKit = (RenderKit) renderKits.get(renderKitId);
+        if (renderKit == null)
+        {
+            // Issue 38294 -- We removed the automatic creation of the
+            // default renderkit in the constructor, allowing it to be
+            // added by AbstractJsfTestCase in the usual case.  To preserve
+            // backwards compatibility, however, create one on the fly
+            // if the user asks for the default HTML renderkit and it has
+            // not been manually added yet
+            if (RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(renderKitId))
+            {
+                renderKit = new MockRenderKit();
+                renderKits.put(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+                        renderKit);
+                return renderKit;
+            }
+            throw new IllegalArgumentException(renderKitId);
+        }
+        return renderKit;
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getRenderKitIds()
+    {
+
+        return renderKits.keySet().iterator();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockRequestDispatcher.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockRequestDispatcher.java
new file mode 100644
index 0000000..4fb22f5
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockRequestDispatcher.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.myfaces.test.mock;

+

+import java.io.IOException;

+import java.net.URL;

+

+import javax.servlet.RequestDispatcher;

+import javax.servlet.ServletException;

+import javax.servlet.ServletRequest;

+import javax.servlet.ServletResponse;

+

+/**

+ * 

+ * @since 1.0.0

+ * @author Jacob Hookom

+ * @version $Id: MockRequestDispatcher.java 804043 2009-08-13 22:08:44Z lu4242 $

+ */

+public class MockRequestDispatcher implements RequestDispatcher

+{

+

+    protected final URL url;

+

+    public MockRequestDispatcher(URL url)

+    {

+        this.url = url;

+    }

+

+    public void forward(ServletRequest request, ServletResponse response)

+            throws ServletException, IOException

+    {

+        // TODO Auto-generated method stub

+

+    }

+

+    public void include(ServletRequest request, ServletResponse response)

+            throws ServletException, IOException

+    {

+        // TODO Auto-generated method stub

+

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseStateManager.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseStateManager.java
new file mode 100644
index 0000000..9688bf9
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseStateManager.java
@@ -0,0 +1,396 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.mock;

+

+import java.io.ByteArrayInputStream;

+import java.io.ByteArrayOutputStream;

+import java.io.IOException;

+import java.io.ObjectInputStream;

+import java.io.ObjectOutputStream;

+

+import javax.faces.FacesException;

+import javax.faces.application.StateManager.SerializedView;

+import javax.faces.context.FacesContext;

+import javax.faces.context.ResponseWriter;

+import javax.faces.render.RenderKitFactory;

+import javax.faces.render.ResponseStateManager;

+

+/**

+ * Mock class that encode view state in hex format

+ * 

+ * @author Leonardo Uribe

+ * @since 1.0.0

+ *

+ */

+public class MockResponseStateManager extends ResponseStateManager

+{

+    // ------------------------------------------------------------ Constructors

+

+    // ------------------------------------------------------ Instance Variables

+

+    private static final int TREE_PARAM = 0;

+    private static final int STATE_PARAM = 1;

+    private static final int VIEWID_PARAM = 2;

+    private static final String ZIP_CHARSET = "ISO-8859-1";

+

+    // ----------------------------------------------------- Mock Object Methods

+

+    // -------------------------------------------------- ResponseStateManager Methods

+

+    public Object getState(FacesContext facesContext, String viewId)

+    {

+        Object[] savedState = getSavedState(facesContext);

+        if (savedState == null)

+        {

+            return null;

+        }

+

+        return new Object[] { savedState[TREE_PARAM], savedState[STATE_PARAM] };

+    }

+

+    public Object getTreeStructureToRestore(FacesContext facesContext,

+            String viewId)

+    {

+        // Although this method won't be called anymore,

+        // it has been kept for backward compatibility.

+        Object[] savedState = getSavedState(facesContext);

+        if (savedState == null)

+        {

+            return null;

+        }

+

+        return savedState[TREE_PARAM];

+    }

+

+    public Object getComponentStateToRestore(FacesContext facesContext)

+    {

+        // Although this method won't be called anymore,

+        // it has been kept for backward compatibility.

+        Object[] savedState = getSavedState(facesContext);

+        if (savedState == null)

+        {

+            return null;

+        }

+

+        return savedState[STATE_PARAM];

+    }

+

+    public boolean isPostback(FacesContext context)

+    {

+        return context.getExternalContext().getRequestParameterMap()

+                .containsKey(ResponseStateManager.VIEW_STATE_PARAM);

+    }

+

+    public void writeState(FacesContext facescontext,

+            SerializedView serializedview) throws IOException

+    {

+        ResponseWriter responseWriter = facescontext.getResponseWriter();

+

+        Object[] savedState = new Object[3];

+

+        if (facescontext.getApplication().getStateManager()

+                .isSavingStateInClient(facescontext))

+        {

+            Object treeStruct = serializedview.getStructure();

+            Object compStates = serializedview.getState();

+

+            if (treeStruct != null)

+            {

+                savedState[TREE_PARAM] = treeStruct;

+            }

+

+            if (compStates != null)

+            {

+                savedState[STATE_PARAM] = compStates;

+            }

+        }

+        else

+        {

+            // write viewSequence

+            Object treeStruct = serializedview.getStructure();

+            if (treeStruct != null)

+            {

+                if (treeStruct instanceof String)

+                {

+                    savedState[TREE_PARAM] = treeStruct;

+                }

+            }

+        }

+

+        savedState[VIEWID_PARAM] = facescontext.getViewRoot().getViewId();

+

+        // write the view state field

+        writeViewStateField(facescontext, responseWriter, savedState);

+

+        // renderKitId field

+        writeRenderKitIdField(facescontext, responseWriter);

+    }

+

+    private void writeViewStateField(FacesContext facesContext,

+            ResponseWriter responseWriter, Object savedState)

+            throws IOException

+    {

+

+        String serializedState = construct(facesContext, savedState);

+        responseWriter.startElement("input", null);

+        responseWriter.writeAttribute("type", "hidden", null);

+        responseWriter.writeAttribute("name", VIEW_STATE_PARAM, null);

+        responseWriter.writeAttribute("value", serializedState, null);

+        responseWriter.endElement("input");

+    }

+

+    private void writeRenderKitIdField(FacesContext facesContext,

+            ResponseWriter responseWriter) throws IOException

+    {

+

+        String defaultRenderKitId = facesContext.getApplication()

+                .getDefaultRenderKitId();

+        if (defaultRenderKitId != null

+                && !RenderKitFactory.HTML_BASIC_RENDER_KIT

+                        .equals(defaultRenderKitId))

+        {

+            responseWriter.startElement("input", null);

+            responseWriter.writeAttribute("type", "hidden", null);

+            responseWriter.writeAttribute("name",

+                    ResponseStateManager.RENDER_KIT_ID_PARAM, null);

+            responseWriter.writeAttribute("value", defaultRenderKitId, null);

+            responseWriter.endElement("input");

+        }

+    }

+

+    private String construct(FacesContext facesContext, Object savedState)

+            throws IOException

+    {

+        byte[] bytes = null;

+        ByteArrayOutputStream baos = null;

+        ObjectOutputStream oos = null;

+        try

+        {

+            baos = new ByteArrayOutputStream();

+            oos = new ObjectOutputStream(baos);

+            oos.writeObject(savedState);

+            bytes = baos.toByteArray();

+        }

+        finally

+        {

+            if (oos != null)

+            {

+                try

+                {

+                    oos.close();

+                }

+                catch (IOException e)

+                {

+                }

+                finally

+                {

+                    oos = null;

+                }

+            }

+            if (baos != null)

+            {

+                try

+                {

+                    baos.close();

+                }

+                catch (IOException e)

+                {

+                }

+                finally

+                {

+                    baos = null;

+                }

+            }

+        }

+        return new String(new _Hex().encode(bytes), ZIP_CHARSET);

+    }

+

+    private Object reconstruct(FacesContext facesContext, String encodedState)

+            throws IOException

+    {

+        byte[] bytes = encodedState.getBytes(ZIP_CHARSET);

+

+        try

+        {

+            bytes = new _Hex().decode(bytes); 

+        }

+        catch(Exception e)

+        {

+            throw new IOException(e.getMessage());

+        }

+

+        ByteArrayInputStream input = null;

+        ObjectInputStream s = null;

+        Object object = null;

+

+        try

+        {

+            input = new ByteArrayInputStream(bytes);

+            s = new ObjectInputStream(input);

+            object = s.readObject();

+        }

+        catch (ClassNotFoundException e)

+        {

+            throw new IOException(e.getMessage());

+        }

+        finally

+        {

+            if (s != null)

+            {

+                try

+                {

+                    s.close();

+                }

+                catch (IOException e)

+                {

+                }

+                finally

+                {

+                    s = null;

+                }

+            }

+            if (input != null)

+            {

+                try

+                {

+                    input.close();

+                }

+                catch (IOException e)

+                {

+                }

+                finally

+                {

+                    input = null;

+                }

+            }

+        }

+        return object;

+    }

+

+    private Object[] getSavedState(FacesContext facesContext)

+    {

+        Object encodedState = facesContext.getExternalContext()

+                .getRequestParameterMap().get(VIEW_STATE_PARAM);

+        if (encodedState == null || (((String) encodedState).length() == 0))

+        {

+            return null;

+        }

+

+        Object[] savedState = null;

+

+        try

+        {

+            savedState = (Object[]) reconstruct(facesContext,

+                    (String) encodedState);

+        }

+        catch (IOException e)

+        {

+            facesContext.getExternalContext().log(

+                    "Cannot reconstruct view state", e);

+        }

+

+        if (savedState == null)

+        {

+            return null;

+        }

+

+        String restoredViewId = (String) savedState[VIEWID_PARAM];

+

+        if (restoredViewId == null)

+        {

+            return null;

+        }

+        return savedState;

+    }

+

+    public String getViewState(FacesContext facesContext, Object state)

+    {

+        if (state == null)

+        {

+            return null;

+        }

+

+        Object treeStruct = null;

+        Object compStates = null;

+

+        if (state instanceof SerializedView)

+        {

+            SerializedView view = (SerializedView) state;

+            treeStruct = view.getStructure();

+            compStates = view.getState();

+        }

+        else if (state instanceof Object[])

+        {

+            Object[] structureAndState = (Object[]) state;

+

+            if (structureAndState.length == 2)

+            {

+                treeStruct = structureAndState[0];

+                compStates = structureAndState[1];

+            }

+            else

+            {

+                throw new FacesException(

+                        "The state should be an array of Object[] of lenght 2");

+            }

+        }

+        else

+        {

+            throw new FacesException(

+                    "The state should be an array of Object[] of lenght 2, or a SerializedView instance");

+        }

+

+        Object[] savedState = new Object[3];

+

+        if (facesContext.getApplication().getStateManager()

+                .isSavingStateInClient(facesContext))

+        {

+            if (treeStruct != null)

+            {

+                savedState[TREE_PARAM] = treeStruct;

+            }

+

+            if (compStates != null)

+            {

+                savedState[STATE_PARAM] = compStates;

+            }

+        }

+        else

+        {

+            // write viewSequence

+            if (treeStruct != null)

+            {

+                if (treeStruct instanceof String)

+                {

+                    savedState[TREE_PARAM] = treeStruct;

+                }

+            }

+        }

+        savedState[VIEWID_PARAM] = facesContext.getViewRoot().getViewId();

+

+        try

+        {

+            return construct(facesContext, savedState);

+        }

+        catch (IOException e)

+        {

+            throw new RuntimeException(e);

+        }

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseWriter.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseWriter.java
new file mode 100644
index 0000000..16f4033
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockResponseWriter.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.test.mock;
+
+import java.io.IOException;
+import java.io.Writer;
+import javax.faces.component.UIComponent;
+import javax.faces.context.ResponseWriter;
+
+/**
+ * <p>Mock implementation of <code>javax.faces.context.ResponseWriter</code>.</p>
+ * 
+ * @since 1.0.0
+ */
+public class MockResponseWriter extends ResponseWriter
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    public MockResponseWriter(Writer writer)
+    {
+        this.writer = writer;
+        this.contentType = "text/html";
+        this.characterEncoding = "UTF-8";
+    }
+
+    /**
+     * <p>Construct an instance wrapping the specified writer.</p>
+     *
+     * @param writer Writer we are wrapping
+     * @param contentType Content type to be created
+     * @param characterEncoding Character encoding of this response
+     */
+    public MockResponseWriter(Writer writer, String contentType,
+            String characterEncoding)
+    {
+        this.writer = writer;
+        this.contentType = contentType;
+        this.characterEncoding = characterEncoding;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private String characterEncoding = null;
+    private String contentType = "text/html";
+    private boolean open = false; // Is an element currently open?
+    private UIComponent component;
+    private Writer writer = null;
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Return the <code>Writer</code> that we are wrapping.</p>
+     */
+    public Writer getWriter()
+    {
+        return this.writer;
+    }
+
+    // -------------------------------------------------- ResponseWriter Methods
+
+    /** {@inheritDoc} */
+    public ResponseWriter cloneWithWriter(Writer writer)
+    {
+        return new MockResponseWriter(writer, contentType, characterEncoding);
+    }
+
+    /** {@inheritDoc} */
+    public void endDocument() throws IOException
+    {
+        finish();
+        writer.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void endElement(String name) throws IOException
+    {
+        if (open)
+        {
+            writer.write("/");
+            finish();
+        }
+        else
+        {
+            writer.write("</");
+            writer.write(name);
+            writer.write(">");
+        }
+        component = null;
+    }
+
+    /** {@inheritDoc} */
+    public String getCharacterEncoding()
+    {
+        return this.characterEncoding;
+    }
+
+    /** {@inheritDoc} */
+    public String getContentType()
+    {
+        return this.contentType;
+    }
+
+    /** {@inheritDoc} */
+    public void flush() throws IOException
+    {
+        finish();
+    }
+
+    /** {@inheritDoc} */
+    public void startDocument() throws IOException
+    {
+        // Do nothing
+    }
+
+    /** {@inheritDoc} */
+    public void startElement(String name, UIComponent component)
+            throws IOException
+    {
+        if (name == null)
+        {
+            throw new NullPointerException();
+        }
+        finish();
+        writer.write('<');
+        writer.write(name);
+        open = true;
+        this.component = component;
+    }
+
+    /** {@inheritDoc} */
+    public void writeAttribute(String name, Object value, String property)
+            throws IOException
+    {
+        if (name == null)
+        {
+            throw new NullPointerException();
+        }
+        if (!open)
+        {
+            throw new IllegalStateException();
+        }
+        String attribute = findValue(value, property);
+        if (attribute != null)
+        {
+            writer.write(" ");
+            writer.write(name);
+            writer.write("=\"");
+            string(attribute);
+
+            writer.write("\"");
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void writeComment(Object comment) throws IOException
+    {
+        if (comment == null)
+        {
+            throw new NullPointerException();
+        }
+        finish();
+        writer.write("<!-- ");
+        if (comment instanceof String)
+        {
+            writer.write((String) comment);
+        }
+        else
+        {
+            writer.write(comment.toString());
+        }
+        writer.write(" -->");
+    }
+
+    /** {@inheritDoc} */
+    public void writeText(Object text, String property) throws IOException
+    {
+        if (text == null)
+        {
+            throw new NullPointerException();
+        }
+        finish();
+        String value = findValue(text, property);
+        if (value != null)
+        {
+            string(value);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void writeText(char[] text, int off, int len) throws IOException
+    {
+        if (text == null)
+        {
+            throw new NullPointerException();
+        }
+        if ((off < 0) || (off > text.length) || (len < 0)
+                || (len > text.length))
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        finish();
+        string(text, off, len);
+    }
+
+    /** {@inheritDoc} */
+    public void writeURIAttribute(String name, Object value, String property)
+            throws IOException
+    {
+        if (name == null)
+        {
+            throw new NullPointerException();
+        }
+        if (!open)
+        {
+            throw new IllegalStateException();
+        }
+        String attribute = findValue(value, property);
+        if (attribute != null)
+        {
+            writer.write(" ");
+            writer.write(name);
+            writer.write("=\"");
+
+            string(attribute);
+            writer.write("\"");
+        }
+    }
+
+    // ---------------------------------------------------------- Writer Methods
+
+    /** {@inheritDoc} */
+    public void close() throws IOException
+    {
+        finish();
+        writer.close();
+    }
+
+    /** {@inheritDoc} */
+    public void write(char[] cbuf, int off, int len) throws IOException
+    {
+        finish();
+        writer.write(cbuf, off, len);
+    }
+
+    // --------------------------------------------------------- Support Methods
+
+    /**
+     * <p>Write the specified character, filtering if necessary.</p>
+     *
+     * @param ch Character to be written
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private void character(char ch) throws IOException
+    {
+
+        if (ch <= 0xff)
+        {
+            // In single byte characters, replace only the five
+            // characters for which well-known entities exist in XML
+            if (ch == 0x22)
+            {
+                writer.write("&quot;");
+            }
+            else if (ch == 0x26)
+            {
+                writer.write("&amp;");
+            }
+            else if (ch == 0x27)
+            {
+                writer.write("&apos;");
+            }
+            else if (ch == 0x3C)
+            {
+                writer.write("&lt;");
+            }
+            else if (ch == 0X3E)
+            {
+                writer.write("&gt;");
+            }
+            else
+            {
+                writer.write(ch);
+            }
+        }
+        else
+        {
+            if (substitution())
+            {
+                numeric(writer, ch);
+            }
+            else
+            {
+                writer.write(ch);
+            }
+        }
+
+    }
+
+    /**
+     * <p>Close any element that is currently open.</p>
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private void finish() throws IOException
+    {
+
+        if (open)
+        {
+            writer.write(">");
+            open = false;
+        }
+
+    }
+
+    /**
+     * <p>Write a numeric character reference for specified character
+     * to the specfied writer.</p>
+     *
+     * @param writer Writer we are writing to
+     * @param ch Character to be translated and appended
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private void numeric(Writer writer, char ch) throws IOException
+    {
+
+        writer.write("&#");
+        writer.write(String.valueOf(ch));
+        writer.write(";");
+
+    }
+
+    /**
+     * <p>Write the specified characters (after performing suitable
+     * replacement of characters by corresponding entities).</p>
+     *
+     * @param text Character array containing text to be written
+     * @param off Starting offset (zero relative)
+     * @param len Number of characters to be written
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private void string(char[] text, int off, int len) throws IOException
+    {
+
+        // Process the specified characters
+        for (int i = off; i < (off + len); i++)
+        {
+            character(text[i]);
+        }
+
+    }
+
+    /**
+     * <p>Write the specified string (after performing suitable
+     * replacement of characters by corresponding entities).</p>
+     *
+     * @param s String to be filtered and written
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private void string(String s) throws IOException
+    {
+        for (int i = 0; i < s.length(); i++)
+        {
+            character(s.charAt(i));
+        }
+
+    }
+
+    /**
+     * <p>Return true if entity substitution should be performed on double
+     * byte character values.</p>
+     */
+    private boolean substitution()
+    {
+
+        return !("UTF-8".equals(characterEncoding) || "UTF-16"
+                .equals(characterEncoding));
+
+    }
+
+    private String findValue(final Object value, final String property)
+    {
+        if (value != null)
+        {
+            return value instanceof String ? (String) value : value.toString();
+        }
+        else if (property != null)
+        {
+            if (component != null)
+            {
+                final Object object = component.getAttributes().get(property);
+                if (object != null)
+                {
+                    return object instanceof String ? (String) object : object
+                            .toString();
+                }
+                else
+                {
+                    return null;
+                }
+            }
+
+        }
+        return null;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServlet.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServlet.java
new file mode 100644
index 0000000..bc0f966
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServlet.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.myfaces.test.mock;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * <p>Mock implementation of <code>Servlet</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockServlet implements Servlet
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Create a default Servlet instance.</p>
+     */
+    public MockServlet()
+    {
+    }
+
+    /**
+     * <p>Create a new Servlet with the specified ServletConfig.</p>
+     *
+     * @param config The new ServletConfig instance
+     */
+    public MockServlet(ServletConfig config) throws ServletException
+    {
+        init(config);
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Set the <code>ServletConfig</code> instance for this servlet.</p>
+     *
+     * @param config The new ServletConfig instance
+     */
+    public void setServletConfig(ServletConfig config)
+    {
+
+        this.config = config;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The <code>ServletConfig</code> instance for this servlet.</p>
+     */
+    private ServletConfig config;
+
+    // --------------------------------------------------------- Servlet Methods
+
+    /** {@inheritDoc} */
+    public void destroy()
+    {
+    }
+
+    /** {@inheritDoc} */
+    public ServletConfig getServletConfig()
+    {
+
+        return this.config;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServletInfo()
+    {
+
+        return "MockServlet";
+
+    }
+
+    /** {@inheritDoc} */
+    public void init(ServletConfig config) throws ServletException
+    {
+
+        this.config = config;
+
+    }
+
+    /** {@inheritDoc} */
+    public void service(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+    {
+
+        // Do nothing by default
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServletConfig.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletConfig.java
new file mode 100644
index 0000000..57c26bd
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletConfig.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.myfaces.test.mock;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * <p>Mock implementation of <code>ServletConfig</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockServletConfig implements ServletConfig
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockServletConfig()
+    {
+    }
+
+    /**
+     * <p>Construct an instance associated with the specified
+     * servlet context.</p>
+     *
+     * @param context The associated ServletContext
+     */
+    public MockServletConfig(ServletContext context)
+    {
+        setServletContext(context);
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a servlet initialization parameter.</p>
+     *
+     * @param name Parameter name
+     * @param value Parameter value
+     */
+    public void addInitParameter(String name, String value)
+    {
+
+        parameters.put(name, value);
+
+    }
+
+    /**
+     * <p>Set the servlet context for this application.</p>
+     *
+     * @param context The new servlet context
+     */
+    public void setServletContext(ServletContext context)
+    {
+
+        this.context = context;
+
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private ServletContext context;
+    private Hashtable parameters = new Hashtable();
+
+    // --------------------------------------------------- ServletConfig Methods
+
+    /** {@inheritDoc} */
+    public String getInitParameter(String name)
+    {
+
+        return (String) parameters.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getInitParameterNames()
+    {
+
+        return parameters.keys();
+
+    }
+
+    /** {@inheritDoc} */
+    public ServletContext getServletContext()
+    {
+
+        return this.context;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getServletName()
+    {
+
+        return "MockServlet";
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServletContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletContext.java
new file mode 100644
index 0000000..9c3d199
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletContext.java
@@ -0,0 +1,746 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+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.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+/**
+ * <p>Mock implementation of <code>ServletContext</code>.</p>
+ *
+ * <p><strong>WARNING</strong> - Before you can get meaningful results from
+ * calls to the <code>getResource()</code>, <code>getResourceAsStream()</code>,
+ * <code>getResourcePaths()</code>, or <code>getRealPath()</code> methods,
+ * you must configure the <code>documentRoot</code> property, passing in a
+ * <code>File</code> object pointing at a directory that simulates a
+ * web application structure.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockServletContext implements ServletContext
+{
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Add a context initialization parameter to the set of
+     * parameters recognized by this instance.</p>
+     *
+     * @param name Parameter name
+     * @param value Parameter value
+     */
+    public void addInitParameter(String name, String value)
+    {
+        parameters.put(name, value);
+    }
+
+    /**
+     * <p>Add a new MIME type mapping to the set of mappings
+     * recognized by this instance.</p>
+     *
+     * @param extension Extension to check for (without the period)
+     * @param contentType Corresponding content type
+     */
+    public void addMimeType(String extension, String contentType)
+    {
+        mimeTypes.put(extension, contentType);
+    }
+
+    /**
+     * <p>Set the document root for <code>getRealPath()</code>
+     * resolution.  This parameter <strong>MUST</strong> represent
+     * a directory.</p>
+     *
+     * @param documentRoot The new base directory
+     */
+    public void setDocumentRoot(File documentRoot)
+    {
+        this.documentRoot = documentRoot;
+    }
+
+    public void setDocumentRoot(URI base)
+    {
+        File f = new File(base);
+        if (!f.exists())
+        {
+            throw new IllegalArgumentException("File: " + base.getPath()
+                    + " doesn't exist");
+        }
+        this.documentRoot = f;
+    }
+
+    /**
+     * <p>Add a new listener instance that should be notified about
+     * attribute changes.</p>
+     *
+     * @param listener Listener to be added
+     */
+    public void addAttributeListener(ServletContextAttributeListener listener)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            attributeListeners.add(listener);
+        }
+        else
+        {
+            container.subscribeListener(listener);
+        }
+    }
+    
+    public MockWebContainer getWebContainer()
+    {
+        return webContainer;
+    }
+    
+    public void setWebContainer(MockWebContainer container)
+    {
+        webContainer = container;
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    private Hashtable attributes = new Hashtable();
+    private File documentRoot = null;
+    private Hashtable mimeTypes = new Hashtable();
+    private Hashtable parameters = new Hashtable();
+    private List attributeListeners = new ArrayList();
+    private MockWebContainer webContainer;
+    private Map<String, ServletRegistration> servletRegistrations = 
+            new HashMap<String, ServletRegistration>();
+    
+    private ClassLoader classLoader;
+
+    // -------------------------------------------------- ServletContext Methods
+
+    /** {@inheritDoc} */
+    public Object getAttribute(String name)
+    {
+
+        return attributes.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getAttributeNames()
+    {
+
+        return attributes.keys();
+
+    }
+
+    /** {@inheritDoc} */
+    public String getInitParameter(String name)
+    {
+
+        return (String) parameters.get(name);
+
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getInitParameterNames()
+    {
+
+        return parameters.keys();
+
+    }
+
+    /** {@inheritDoc} */
+    public int getMajorVersion()
+    {
+
+        return 2;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getMimeType(String path)
+    {
+
+        int period = path.lastIndexOf('.');
+        if (period < 0)
+        {
+            return null;
+        }
+        String extension = path.substring(period + 1);
+        return (String) mimeTypes.get(extension);
+
+    }
+
+    /** {@inheritDoc} */
+    public int getMinorVersion()
+    {
+
+        return 4;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getRealPath(String path)
+    {
+
+        if (documentRoot != null)
+        {
+            if (!path.startsWith("/"))
+            {
+                throw new IllegalArgumentException("The specified path ('"
+                        + path + "') does not start with a '/' character");
+            }
+            File resolved = new File(documentRoot, path.substring(1));
+            try
+            {
+                return resolved.getCanonicalPath();
+            }
+            catch (IOException e)
+            {
+                return resolved.getAbsolutePath();
+            }
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public URL getResource(String path) throws MalformedURLException
+    {
+
+        if (documentRoot != null)
+        {
+            if (!path.startsWith("/"))
+            {
+                throw new MalformedURLException("The specified path ('" + path
+                        + "') does not start with a '/' character");
+            }
+            File resolved = new File(documentRoot, path.substring(1));
+            if (resolved.exists())
+            {
+                return resolved.toURL();
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public InputStream getResourceAsStream(String path)
+    {
+        try
+        {
+            URL url = getResource(path);
+            if (url != null)
+            {
+                return url.openStream();
+            }
+        }
+        catch (Exception e)
+        {
+            //No op
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Set getResourcePaths(String path)
+    {
+
+        if (documentRoot == null)
+        {
+            return null;
+        }
+
+        // Enforce the leading slash restriction
+        if (!path.startsWith("/"))
+        {
+            throw new IllegalArgumentException("The specified path ('" + path
+                    + "') does not start with a '/' character");
+        }
+
+        // Locate the File node for this path's directory (if it exists)
+        File node = new File(documentRoot, path.substring(1));
+        if (!node.exists())
+        {
+            return null;
+        }
+        if (!node.isDirectory())
+        {
+            return null;
+        }
+
+        // Construct a Set containing the paths to the contents of this directory
+        Set set = new HashSet();
+        String[] files = node.list();
+        if (files == null)
+        {
+            return null;
+        }
+        for (int i = 0; i < files.length; i++)
+        {
+            String subfile = path + files[i];
+            File subnode = new File(node, files[i]);
+            if (subnode.isDirectory())
+            {
+                subfile += "/";
+            }
+            set.add(subfile);
+        }
+
+        // Return the completed set
+        return set;
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message)
+    {
+        System.out.println(message);
+    }
+
+    /** {@inheritDoc} */
+    public void log(Exception exception, String message)
+    {
+        System.out.println(message);
+        exception.printStackTrace();
+    }
+
+    /** {@inheritDoc} */
+    public void log(String message, Throwable exception)
+    {
+        System.out.println(message);
+        exception.printStackTrace();
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(String name)
+    {
+        if (attributes.containsKey(name))
+        {
+            Object value = attributes.remove(name);
+            fireAttributeRemoved(name, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(String name, Object value)
+    {
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Attribute name cannot be null");
+        }
+        if (value == null)
+        {
+            removeAttribute(name);
+            return;
+        }
+        if (attributes.containsKey(name))
+        {
+            Object oldValue = attributes.get(name);
+            attributes.put(name, value);
+            fireAttributeReplaced(name, oldValue);
+        }
+        else
+        {
+            attributes.put(name, value);
+            fireAttributeAdded(name, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public ServletContext getContext(String uripath)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getContextPath()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public Servlet getServlet(String name) throws ServletException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getServletContextName()
+    {
+        return "MockServletContext";
+    }
+
+    /** {@inheritDoc} */
+    public String getServerInfo()
+    {
+        return "MockServletContext";
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getServlets()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration getServletNames()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public RequestDispatcher getNamedDispatcher(String name)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        URI uri = this.resolve(path);
+        if (uri != null)
+        {
+            File f = new File(uri);
+            if (f.exists())
+            {
+                try
+                {
+                    return new MockRequestDispatcher(uri.toURL());
+                }
+                catch (MalformedURLException e)
+                {
+                    this.log(e.getMessage());
+                    return null;
+                }
+            }
+
+        }
+        return null;
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    private final URI resolve(String path)
+    {
+        if (path == null)
+        {
+            throw new NullPointerException("Path cannot be null");
+        }
+        if (path.charAt(0) == '/')
+        {
+            if (path.length() > 1)
+            {
+                return documentRoot.toURI().resolve(path.substring(1));
+            }
+            return documentRoot.toURI();
+        }
+        return null;
+    }
+
+    /**
+     * <p>Fire an attribute added event to interested listeners.</p>
+     *
+     * @param key Attribute whose value has been added
+     * @param value The new value
+     */
+    void fireAttributeAdded(String key, Object value)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {        
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                ServletContextAttributeListener listener = (ServletContextAttributeListener) listeners
+                        .next();
+                listener.attributeAdded(event);
+            }
+        }
+        else
+        {
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);
+            container.attributeAdded(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute removed event to interested listeners.</p>
+     *
+     * @param key Attribute whose value has been removed
+     * @param value The value that was removed
+     */
+    void fireAttributeRemoved(String key, Object value)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                ServletContextAttributeListener listener = (ServletContextAttributeListener) listeners
+                        .next();
+                listener.attributeRemoved(event);
+            }
+        }
+        else
+        {
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);
+            container.attributeRemoved(event);
+        }
+    }
+
+    /**
+     * <p>Fire an attribute replaced event to interested listeners.</p>
+     *
+     * @param key Attribute whose value has been replaced
+     * @param value The original value
+     */
+    void fireAttributeReplaced(String key, Object value)
+    {
+        MockWebContainer container = getWebContainer();
+        if (container == null)
+        {
+            if (attributeListeners.size() < 1)
+            {
+                return;
+            }
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);
+            Iterator listeners = attributeListeners.iterator();
+            while (listeners.hasNext())
+            {
+                ServletContextAttributeListener listener = (ServletContextAttributeListener) listeners
+                        .next();
+                listener.attributeReplaced(event);
+            }
+        }
+        else
+        {
+            ServletContextAttributeEvent event = new ServletContextAttributeEvent(
+                    this, key, value);            
+            container.attributeReplaced(event);
+        }
+    }
+
+    public boolean setInitParameter(String name, String value)
+    {
+        addInitParameter(name, value);
+        return true;
+    }
+
+    public ServletRegistration.Dynamic addServlet(String name, String string1) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public ServletRegistration.Dynamic addServlet(String name, Servlet srvlt) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public ServletRegistration.Dynamic addServlet(String name, Class<? extends Servlet> type) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public <T extends Servlet> T createServlet(Class<T> type) throws ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public ServletRegistration getServletRegistration(String name)
+    {
+        return servletRegistrations.get(name);
+    }
+
+    public Map<String, ? extends ServletRegistration> getServletRegistrations()
+    {
+        return servletRegistrations;
+    }
+    
+    public void addServletRegistration(String name, String servletClassName, String ... mappings)
+    {
+        ServletRegistration sr = new MockServletRegistration(name, servletClassName, mappings);
+        this.servletRegistrations.put(name, sr);
+    }
+
+    public FilterRegistration.Dynamic addFilter(String string, String string1) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public FilterRegistration.Dynamic addFilter(String string, Filter filter) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public FilterRegistration.Dynamic addFilter(String string, Class<? extends Filter> type) 
+            throws IllegalArgumentException, IllegalStateException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public <T extends Filter> T createFilter(Class<T> type) throws ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public FilterRegistration getFilterRegistration(String string)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public void addListener(Class<? extends EventListener> type)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public void addListener(String string)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public <T extends EventListener> void addListener(T t)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public <T extends EventListener> T createListener(Class<T> type) throws ServletException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public void declareRoles(String... strings)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public SessionCookieConfig getSessionCookieConfig()
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public void setSessionTrackingModes(Set<SessionTrackingMode> set)
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public int getEffectiveMajorVersion() throws UnsupportedOperationException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public int getEffectiveMinorVersion() throws UnsupportedOperationException
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+
+    public ClassLoader getClassLoader()
+    {
+        if (classLoader != null)
+        {
+            return classLoader;
+        }
+        else
+        {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+    
+    public void setClassLoader(ClassLoader classLoader)
+    {
+        this.classLoader = classLoader;
+    }
+
+    public JspConfigDescriptor getJspConfigDescriptor()
+    {
+        throw new UnsupportedOperationException("Not supported yet."); 
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServletInputStream.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletInputStream.java
new file mode 100644
index 0000000..4a57bf0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletInputStream.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.myfaces.test.mock;

+

+import java.io.ByteArrayInputStream;

+import java.io.IOException;

+import java.io.InputStream;

+

+import javax.servlet.ServletInputStream;

+

+/**

+ * 

+ * @author Jacob Hookom

+ * @version $Id: MockServletInputStream.java 804043 2009-08-13 22:08:44Z lu4242 $

+ * @since 1.0.0

+ */

+public class MockServletInputStream extends ServletInputStream

+{

+

+    private final InputStream source;

+

+    public MockServletInputStream()

+    {

+        this.source = new ByteArrayInputStream(new byte[0]);

+    }

+

+    public MockServletInputStream(InputStream source)

+    {

+        this.source = source;

+    }

+

+    public int read() throws IOException

+    {

+        return this.source.read();

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServletOutputStream.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletOutputStream.java
new file mode 100644
index 0000000..676bf00
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletOutputStream.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.myfaces.test.mock;
+
+import java.io.ByteArrayOutputStream;
+import javax.servlet.ServletOutputStream;
+
+/**
+ * <p>Mock implementation of <code>ServletOutputStream</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockServletOutputStream extends ServletOutputStream
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     *
+     * @param stream The stream we will use to buffer output
+     */
+    public MockServletOutputStream(ByteArrayOutputStream stream)
+    {
+        this.baos = stream;
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    /**
+     * <p>Return the content that has been written to this output stream.</p>
+     */
+    public byte[] content()
+    {
+        return baos.toByteArray();
+    }
+
+    /**
+     * <p>Reset this output stream so that it appears no content has been
+     * written.</p>
+     */
+    public void reset()
+    {
+        baos.reset();
+    }
+
+    /**
+     * <p>Return the number of bytes that have been written to this output stream.</p>
+     */
+    public int size()
+    {
+        return baos.size();
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The internal buffer we use to capture output.</p>
+     */
+    private ByteArrayOutputStream baos = null;
+
+    // --------------------------------------------- ServletOutputStream Methods
+
+    /**
+     * <p>Write the specified content to our internal cache.</p>
+     *
+     * @param content Content to be written
+     */
+    public void write(int content)
+    {
+        baos.write(content);
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockServletRegistration.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletRegistration.java
new file mode 100644
index 0000000..a639c69
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockServletRegistration.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.myfaces.test.mock;

+

+import java.util.Collection;

+import java.util.LinkedHashSet;

+import java.util.Map;

+import java.util.Set;

+import javax.servlet.ServletRegistration;

+

+/**

+ *

+ */

+public class MockServletRegistration implements ServletRegistration

+{

+    public static String[] facesServletMappings = {"/faces/*", "*.jsf", "*.xhtml"};

+    

+    private Set<String> mappings = new LinkedHashSet<String>();

+    private String name;

+    private String servletClassName;

+

+    public MockServletRegistration(String name, String servletClassName, String ... mappings)

+    {

+        this.name = name;

+        this.servletClassName = servletClassName;

+    }

+

+    public MockServletRegistration()

+    {

+    }

+        

+    public Set<String> addMapping(String... mappingArray)

+    {

+        for (String s : mappingArray)

+        {

+            mappings.add(s);

+        }

+        return mappings;

+    }

+

+    public Collection<String> getMappings()

+    {

+        return mappings;

+    }

+

+    public String getRunAsRole()

+    {

+        throw new UnsupportedOperationException("Not supported yet.");

+    }

+

+    public String getClassName()

+    {

+        return servletClassName;

+    }

+

+    public String getInitParameter(String string)

+    {

+        throw new UnsupportedOperationException("Not supported yet.");

+    }

+

+    public Map<String, String> getInitParameters()

+    {

+        throw new UnsupportedOperationException("Not supported yet.");

+    }

+

+    public String getName()

+    {

+        return name;

+    }

+

+    public boolean setInitParameter(String string, String string1)

+    {

+        throw new UnsupportedOperationException("Not supported yet.");

+    }

+

+    public Set<String> setInitParameters(Map<String, String> map)

+    {

+        throw new UnsupportedOperationException("Not supported yet.");

+    }

+    

+    public void setClassName(String className)

+    {

+        this.servletClassName = className;

+    }

+    

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockStateManager.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockStateManager.java
new file mode 100644
index 0000000..07d1445
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockStateManager.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.myfaces.test.mock;
+
+import java.io.IOException;
+
+import javax.faces.application.StateManager;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>StateManager</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+public class MockStateManager extends StateManager
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockStateManager()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    // ---------------------------------------------------- StateManager Methods
+
+    /** {@inheritDoc} */
+    public SerializedView saveSerializedView(FacesContext context)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Object getTreeStructureToSave(FacesContext context)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public Object getComponentStateToSave(FacesContext context)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void writeState(FacesContext context, SerializedView view)
+            throws IOException
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public UIViewRoot restoreView(FacesContext context, String viewId,
+            String renderKitId)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public UIViewRoot restoreTreeStructure(FacesContext context, String viewId,
+            String renderKitId)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void restoreComponentState(FacesContext context, UIViewRoot view,
+            String renderKitId)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.java
new file mode 100644
index 0000000..5298b59
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.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.myfaces.test.mock;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * NOTE: Class copied from trinidad to be used on MockFlash.
+ * <p/>
+ * Map that wraps another to provide an isolated namespace using
+ * a prefix.  This is especially handy for storing properties on
+ * the session in a structured manner without putting them into
+ * a true "Map" - because storing in a Map breaks session failover.
+ * (Session failover won't trigger on mutations of contained objects.)
+ * <p/>
+ * Note that there is a potential design flaw;  if you create a SubKeyMap
+ * for "mypackage.foo" and for "mypackage.foo.bar", all the keys in the
+ * latter will actually show up in the former (prefixed by ".bar").  This
+ * "flaw" is actually relied on by PageFlowScopeMap (since it provides
+ * a handy way to clear out all descendents), so don't "fix" it!
+ * 
+ * @since 1.0.0
+ */
+final class MockSubKeyMap<V> extends AbstractMap<String, V>
+{
+    public MockSubKeyMap(Map<String, Object> base, String prefix)
+    {
+        if (base == null)
+        {
+            throw new NullPointerException();
+        }
+        if (prefix == null)
+        {
+            throw new NullPointerException();
+        }
+
+        // Optimize the scenario where we're wrapping another SubKeyMap
+        if (base instanceof MockSubKeyMap)
+        {
+            _base = ((MockSubKeyMap) base)._base;
+            _prefix = ((MockSubKeyMap) base)._prefix + prefix;
+        }
+        else
+        {
+            _base = base;
+            _prefix = prefix;
+        }
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return entrySet().isEmpty();
+    }
+
+    @Override
+    public V get(Object key)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.get(key);
+    }
+
+    @Override
+    public V put(String key, V value)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.put(key, value);
+    }
+
+    @Override
+    public V remove(Object key)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.remove(key);
+    }
+
+    @Override
+    public boolean containsKey(Object key)
+    {
+        if (!(key instanceof String))
+        {
+            return false;
+        }
+
+        return _base.containsKey(_getBaseKey(key));
+    }
+
+    @Override
+    public Set<Map.Entry<String, V>> entrySet()
+    {
+        if (_entrySet == null)
+        {
+            _entrySet = new Entries<V>();
+        }
+        return _entrySet;
+    }
+
+    private String _getBaseKey(Object key)
+    {
+        if (key == null)
+        {
+            throw new NullPointerException();
+        }
+        // Yes, I want a ClassCastException if it's not a String
+        return _prefix + ((String) key);
+    }
+
+    private List<String> _gatherKeys()
+    {
+        List<String> list = new ArrayList<String>();
+        for (String key : _base.keySet())
+        {
+            if (key != null && key.startsWith(_prefix))
+            {
+                list.add(key);
+            }
+        }
+
+        return list;
+    }
+
+    //
+    // Set implementation for SubkeyMap.entrySet()
+    //
+
+    private class Entries<V> extends AbstractSet<Map.Entry<String, V>>
+    {
+        public Entries()
+        {
+        }
+
+        @Override
+        public Iterator<Map.Entry<String, V>> iterator()
+        {
+            // Sadly, if you just try to use a filtering approach
+            // on the iterator, you'll get concurrent modification
+            // exceptions.  Consequently, gather the keys in a list
+            // and iterator over that.
+            List<String> keyList = _gatherKeys();
+            return new EntryIterator<V>(keyList.iterator());
+        }
+
+        @Override
+        public int size()
+        {
+            int size = 0;
+            for (String key : _base.keySet())
+            {
+                if (key != null && key.startsWith(_prefix))
+                {
+                    size++;
+                }
+            }
+
+            return size;
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            Iterator<String> keys = _base.keySet().iterator();
+            while (keys.hasNext())
+            {
+                String key = keys.next();
+                // Short-circuit:  the default implementation would always
+                // need to iterate to find the total size.
+                if (key != null && key.startsWith(_prefix))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public void clear()
+        {
+            Iterator<String> keys = _base.keySet().iterator();
+            while (keys.hasNext())
+            {
+                String key = keys.next();
+                if (key != null && key.startsWith(_prefix))
+                {
+                    keys.remove();
+                }
+            }
+        }
+    }
+
+    private class EntryIterator<V> implements Iterator<Map.Entry<String, V>>
+    {
+        public EntryIterator(Iterator<String> iterator)
+        {
+            _iterator = iterator;
+        }
+
+        public boolean hasNext()
+        {
+            return _iterator.hasNext();
+        }
+
+        public Map.Entry<String, V> next()
+        {
+            String baseKey = _iterator.next();
+            _currentKey = baseKey;
+            return new Entry<V>(baseKey);
+        }
+
+        public void remove()
+        {
+            if (_currentKey == null)
+            {
+                throw new IllegalStateException();
+            }
+
+            _base.remove(_currentKey);
+
+            _currentKey = null;
+        }
+
+        private Iterator<String> _iterator;
+        private String _currentKey;
+    }
+
+    private class Entry<V> implements Map.Entry<String, V>
+    {
+        public Entry(String baseKey)
+        {
+            _baseKey = baseKey;
+        }
+
+        public String getKey()
+        {
+            if (_key == null)
+            {
+                _key = _baseKey.substring(_prefix.length());
+            }
+            return _key;
+        }
+
+        public V getValue()
+        {
+            return (V) _base.get(_baseKey);
+        }
+
+        public V setValue(V value)
+        {
+            return (V) _base.put(_baseKey, value);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean equals(Object o)
+        {
+            if (!(o instanceof Map.Entry))
+            {
+                return false;
+            }
+            Map.Entry<String, V> e = (Map.Entry<String, V>) o;
+            return _equals(getKey(), e.getKey())
+                    && _equals(getValue(), e.getValue());
+        }
+
+        @Override
+        public int hashCode()
+        {
+            Object key = getKey();
+            Object value = getValue();
+            return ((key == null) ? 0 : key.hashCode())
+                    ^ ((value == null) ? 0 : value.hashCode());
+        }
+
+        private String _baseKey;
+        private String _key;
+    }
+
+    static private boolean _equals(Object a, Object b)
+    {
+        if (a == null)
+        {
+            return b == null;
+        }
+        return a.equals(b);
+    }
+
+    private final Map<String, Object> _base;
+    private final String _prefix;
+    private Set<Map.Entry<String, V>> _entrySet;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler.java
new file mode 100644
index 0000000..5e3aa4e
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler.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.myfaces.test.mock;
+
+import java.util.Locale;
+
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+import javax.faces.render.RenderKitFactory;
+
+/**
+ * <p>Mock implementation of <code>ViewHandler</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockViewHandler extends ViewHandler
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a default instance.</p>
+     */
+    public MockViewHandler()
+    {
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    // ----------------------------------------------------- ViewHandler Methods
+
+    /** {@inheritDoc} */
+    public Locale calculateLocale(FacesContext context)
+    {
+
+        Locale locale = context.getApplication().getDefaultLocale();
+        if (locale == null)
+        {
+            locale = Locale.getDefault();
+        }
+        return locale;
+
+    }
+
+    /** {@inheritDoc} */
+    public String calculateRenderKitId(FacesContext context)
+    {
+
+        String renderKitId = context.getApplication().getDefaultRenderKitId();
+        if (renderKitId == null)
+        {
+            renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
+        }
+        return renderKitId;
+
+    }
+
+    /** {@inheritDoc} */
+    public UIViewRoot createView(FacesContext context, String viewId)
+    {
+
+        // Save locale and renderKitId from previous view (if any), per spec
+        Locale locale = null;
+        String renderKitId = null;
+        if (context.getViewRoot() != null)
+        {
+            locale = context.getViewRoot().getLocale();
+            renderKitId = context.getViewRoot().getRenderKitId();
+        }
+
+        // Configure a new UIViewRoot instance
+        UIViewRoot view = new UIViewRoot();
+        view.setViewId(viewId);
+        if (locale != null)
+        {
+            view.setLocale(locale);
+        }
+        else
+        {
+            view.setLocale(context.getApplication().getViewHandler()
+                    .calculateLocale(context));
+        }
+        if (renderKitId != null)
+        {
+            view.setRenderKitId(renderKitId);
+        }
+        else
+        {
+            view.setRenderKitId(context.getApplication().getViewHandler()
+                    .calculateRenderKitId(context));
+        }
+
+        // Return the configured instance
+        return view;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getActionURL(FacesContext context, String viewId)
+    {
+
+        return FacesContext.getCurrentInstance().getExternalContext()
+                .getRequestContextPath()
+                + viewId;
+
+    }
+
+    /** {@inheritDoc} */
+    public String getResourceURL(FacesContext context, String path)
+    {
+
+        return FacesContext.getCurrentInstance().getExternalContext()
+                .getRequestContextPath()
+                + path;
+
+    }
+
+    /** {@inheritDoc} */
+    public void renderView(FacesContext context, UIViewRoot view)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public UIViewRoot restoreView(FacesContext context, String viewId)
+    {
+
+        throw new UnsupportedOperationException();
+
+    }
+
+    /** {@inheritDoc} */
+    public void writeState(FacesContext context)
+    {
+
+    }
+    
+    public String getWebsocketURL(FacesContext context, String channelAndToken)
+    {
+        String url = context.getExternalContext().getRequestContextPath() + 
+                "/javax.faces.push/"+channelAndToken;
+        return url;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler20.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler20.java
new file mode 100644
index 0000000..3384f26
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockViewHandler20.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.myfaces.test.mock;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+/**
+ * Mock for a JSF 2.0 ViewHandler.
+ * This ViewHandler is used by MockApplication20.
+ * 
+ * @author Jakob Korherr (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ * @since 1.0.0
+ */
+public class MockViewHandler20 extends MockViewHandler
+{
+
+    @Override
+    public String getBookmarkableURL(FacesContext context, String viewId,
+            Map<String, List<String>> parameters, boolean includeViewParams)
+    {
+        // the standard impl only calls getActionURL(context, viewId)
+        // but we want to include the parameters too
+
+        String actionEncodedViewId = getActionURL(context, viewId);
+        ExternalContext externalContext = context.getExternalContext();
+        String bookmarkEncodedURL = externalContext.encodeBookmarkableURL(
+                actionEncodedViewId, parameters);
+        return externalContext.encodeActionURL(bookmarkEncodedURL);
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockWebContainer.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockWebContainer.java
new file mode 100644
index 0000000..108bcea
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockWebContainer.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.myfaces.test.mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+/**
+ * 
+ */
+public class MockWebContainer 
+    implements ServletContextListener, ServletContextAttributeListener,
+               ServletRequestListener, ServletRequestAttributeListener,
+               HttpSessionListener, HttpSessionAttributeListener
+{
+    
+    // context
+    private List<ServletContextListener> contextListeners;
+    private List<ServletContextAttributeListener> contextAttributeListeners;
+    
+    // request
+    private List<ServletRequestListener> requestListeners;
+    private List<ServletRequestAttributeListener> requestAttributeListeners;
+    
+    // session
+    private List<HttpSessionListener> sessionListeners;
+    private List<HttpSessionAttributeListener> sessionAttributeListeners;
+
+    public MockWebContainer()
+    {
+    }
+    
+    /**
+     * Create an instance of the passes class and subscribe it as as servlet listener
+     * 
+     * @param listenerClassName
+     * @throws ClassNotFoundException
+     * @throws InstantiationException
+     * @throws IllegalAccessException 
+     */
+    public void subscribeListener(String listenerClassName) 
+        throws ClassNotFoundException, InstantiationException, IllegalAccessException
+    {
+        Class clazz = this.getClass().getClassLoader().loadClass(listenerClassName);
+        Object instance = clazz.newInstance();
+        subscribeListener(instance);
+    }
+    
+    /**
+     * Subscribe a servlet listener
+     * 
+     * @param listener 
+     */
+    public void subscribeListener(Object listener)
+    {
+        if (listener instanceof ServletContextListener)
+        {
+            if (contextListeners == null)
+            {
+                contextListeners = new ArrayList<ServletContextListener>();
+            }
+            contextListeners.add((ServletContextListener)listener);
+        }
+        if (listener instanceof ServletContextAttributeListener)
+        {
+            if (contextAttributeListeners == null)
+            {
+                contextAttributeListeners = new ArrayList<ServletContextAttributeListener>();
+            }
+            contextAttributeListeners.add((ServletContextAttributeListener)listener);
+        }
+        if (listener instanceof ServletRequestListener)
+        {
+            if (requestListeners == null)
+            {
+                requestListeners = new ArrayList<ServletRequestListener>();
+            }
+            requestListeners.add((ServletRequestListener)listener);
+        }
+        if (listener instanceof ServletRequestAttributeListener)
+        {
+            if (requestAttributeListeners == null)
+            {
+                requestAttributeListeners = new ArrayList<ServletRequestAttributeListener>();
+            }
+            requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+        }
+        if (listener instanceof HttpSessionListener)
+        {
+            if (sessionListeners == null)
+            {
+                sessionListeners = new ArrayList<HttpSessionListener>();
+            }
+            sessionListeners.add((HttpSessionListener)listener);
+        }
+        if (listener instanceof HttpSessionAttributeListener)
+        {
+            if (sessionAttributeListeners == null)
+            {
+                sessionAttributeListeners = new ArrayList<HttpSessionAttributeListener>();
+            }
+            sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+        }
+    }
+    
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        if (contextListeners != null && !contextListeners.isEmpty())
+        {
+            for (ServletContextListener listener : contextListeners)
+            {
+                listener.contextInitialized(sce);
+            }
+        }
+    }
+    
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        if (contextListeners != null && !contextListeners.isEmpty())
+        {
+            for (ServletContextListener listener : contextListeners)
+            {
+                listener.contextDestroyed(sce);
+            }
+        }
+    }
+    
+    public void attributeAdded(ServletContextAttributeEvent scab)
+    {
+        if (contextAttributeListeners != null && !contextAttributeListeners.isEmpty())
+        {
+            for (ServletContextAttributeListener listener : contextAttributeListeners)
+            {
+                listener.attributeAdded(scab);
+            }
+        }
+    }
+
+    public void attributeRemoved(ServletContextAttributeEvent scab)
+    {
+        if (contextAttributeListeners != null && !contextAttributeListeners.isEmpty())
+        {
+            for (ServletContextAttributeListener listener : contextAttributeListeners)
+            {
+                listener.attributeRemoved(scab);
+            }
+        }
+    }
+
+    public void attributeReplaced(ServletContextAttributeEvent scab)
+    {
+        if (contextAttributeListeners != null && !contextAttributeListeners.isEmpty())
+        {
+            for (ServletContextAttributeListener listener : contextAttributeListeners)
+            {
+                listener.attributeReplaced(scab);
+            }
+        }
+    }
+    
+    public void requestInitialized ( ServletRequestEvent sre )
+    {
+        if (requestListeners != null && !requestListeners.isEmpty())
+        {
+            for (ServletRequestListener listener : requestListeners)
+            {
+                listener.requestInitialized(sre);
+            }
+        }        
+    }
+    
+    public void requestDestroyed ( ServletRequestEvent sre )
+    {
+        if (requestListeners != null && !requestListeners.isEmpty())
+        {
+            for (ServletRequestListener listener : requestListeners)
+            {
+                listener.requestDestroyed(sre);
+            }
+        }
+    }
+
+    public void sessionCreated ( HttpSessionEvent se )
+    {
+        if (sessionListeners != null && !sessionListeners.isEmpty())
+        {
+            for (HttpSessionListener listener : sessionListeners)
+            {
+                listener.sessionCreated(se);
+            }
+        }
+    }
+    
+    public void sessionDestroyed ( HttpSessionEvent se )
+    {
+        if (sessionListeners != null && !sessionListeners.isEmpty())
+        {
+            for (HttpSessionListener listener : sessionListeners)
+            {
+                listener.sessionDestroyed(se);
+            }
+        }        
+    }
+
+    public void attributeAdded(ServletRequestAttributeEvent srae)
+    {
+        if (requestAttributeListeners != null && !requestAttributeListeners.isEmpty())
+        {
+            for (ServletRequestAttributeListener listener : requestAttributeListeners)
+            {
+                listener.attributeAdded(srae);
+            }
+        }
+    }
+
+    public void attributeRemoved(ServletRequestAttributeEvent srae)
+    {
+        if (requestAttributeListeners != null && !requestAttributeListeners.isEmpty())
+        {
+            for (ServletRequestAttributeListener listener : requestAttributeListeners)
+            {
+                listener.attributeRemoved(srae);
+            }
+        }
+    }
+
+    public void attributeReplaced(ServletRequestAttributeEvent srae)
+    {
+        if (requestAttributeListeners != null && !requestAttributeListeners.isEmpty())
+        {
+            for (ServletRequestAttributeListener listener : requestAttributeListeners)
+            {
+                listener.attributeReplaced(srae);
+            }
+        }
+    }
+
+    public void attributeAdded(HttpSessionBindingEvent se)
+    {
+        if (sessionAttributeListeners != null && !sessionAttributeListeners.isEmpty())
+        {
+            for (HttpSessionAttributeListener listener : sessionAttributeListeners)
+            {
+                listener.attributeAdded(se);
+            }
+        }
+    }
+
+    public void attributeRemoved(HttpSessionBindingEvent se)
+    {
+        if (sessionAttributeListeners != null && !sessionAttributeListeners.isEmpty())
+        {
+            for (HttpSessionAttributeListener listener : sessionAttributeListeners)
+            {
+                listener.attributeRemoved(se);
+            }
+        }
+    }
+
+    public void attributeReplaced(HttpSessionBindingEvent se)
+    {
+        if (sessionAttributeListeners != null && !sessionAttributeListeners.isEmpty())
+        {
+            for (HttpSessionAttributeListener listener : sessionAttributeListeners)
+            {
+                listener.attributeReplaced(se);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/MockedJsfTestContainer.java b/test30/src/main/java/org/apache/myfaces/test/mock/MockedJsfTestContainer.java
new file mode 100644
index 0000000..0a54621
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/MockedJsfTestContainer.java
@@ -0,0 +1,415 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.test.mock;
+
+import java.util.Locale;
+import javax.faces.FactoryFinder;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.component.UIViewRoot;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.RenderKitFactory;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+import org.apache.myfaces.test.config.ResourceBundleVarNames;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;
+import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;
+
+/**
+ *
+ */
+public class MockedJsfTestContainer implements HttpSessionListener
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Construct a new instance of this test case.</p>
+     *
+     * @param name Name of this test case
+     */
+    public MockedJsfTestContainer()
+    {
+    }
+
+    // ---------------------------------------------------- Overall Test Methods
+
+    /**
+     * <p>Set up instance variables required by this test case.</p>
+     */
+    public void setUp()
+    {
+        // Set up Servlet API Objects
+        setUpServletContext();
+
+        // Set up JSF API Objects
+        FactoryFinder.releaseFactories();
+
+        setFactories();
+
+        setUpJSFObjects();
+    }
+    
+    public void setUpAll()
+    {
+        setUp();
+        startRequest();
+    }
+    
+    public void tearDownAll()
+    {
+        endRequest();
+        tearDownRequest();
+    }
+
+    /**
+     * <p>Setup JSF object used for the test. By default it calls to the following
+     * methods in this order:</p>
+     * 
+     * <ul>
+     * <li><code>setUpLifecycle();</code></li>
+     * <li><code>setUpApplication();</code></li>
+     * <li><code>setUpRenderKit();</code></li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpJSFObjects()
+    {
+        setUpLifecycle();
+        setUpApplication();
+        setUpRenderKit();
+    }
+
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>config</code> (<code>MockServletConfig</code>)</li>
+     * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpServletContext()
+    {
+        servletContext = new MockServletContext();
+        config = new MockServletConfig(servletContext);
+        webContainer = new MockWebContainer();
+        servletContext.setWebContainer(webContainer);
+        // Subscribe the container to receive session creation and destroy events.
+        webContainer.subscribeListener(this);
+    }
+    
+    /**
+     * <p>Setup servlet objects that will be used for the test:</p>
+     * 
+     * <ul>
+     * <li><code>request</code> (<code>MockHttpServletRequest</code></li>
+     * <li><code>response</code> (<code>MockHttpServletResponse</code>)</li>
+     * </ul>
+     * 
+     * @throws Exception
+     */
+    protected void setUpRequest()
+    {
+        request = lastSession == null ? 
+            new MockHttpServletRequest() : new MockHttpServletRequest(lastSession);
+        requestInitializedCalled = false;
+        request.setServletContext(servletContext);
+        response = new MockHttpServletResponse();
+    }
+    
+    protected void doRequestInitialized()
+    {
+        if (!requestInitializedCalled)
+        {
+            webContainer.requestInitialized(new ServletRequestEvent(servletContext, request));
+            requestInitializedCalled = true;
+        }
+    }
+    
+    public void startRequest()
+    {
+        setUpRequest();        
+        doRequestInitialized();
+        
+        setUpFacesContext();
+        setUpDefaultView();
+    }
+    
+    public void startSession()
+    {
+        if (request != null)
+        {
+            //Create it indirectly through call to getSession(...)
+            request.getSession(true);
+        }
+    }
+    
+    public void endSession()
+    {
+        MockHttpSession session = (MockHttpSession) request.getSession(false);
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+    
+    public void sessionCreated(HttpSessionEvent se)
+    {
+        lastSession = (MockHttpSession) se.getSession();
+        //No op
+    }
+
+    public void sessionDestroyed(HttpSessionEvent se)
+    {
+        lastSession = null;
+    }
+    
+    /**
+     * <p>Set JSF factories using FactoryFinder method setFactory.</p>
+     * 
+     * @throws Exception
+     */
+    protected void setFactories()
+    {
+        FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
+                "org.apache.myfaces.test.mock.MockApplicationFactory");
+        FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockFacesContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY,
+                "org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory");
+        FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY,
+                "org.apache.myfaces.test.mock.MockRenderKitFactory");
+        FactoryFinder.setFactory(FactoryFinder.EXCEPTION_HANDLER_FACTORY,
+                "org.apache.myfaces.test.mock.MockExceptionHandlerFactory");
+        FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.MockPartialViewContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY,
+                "org.apache.myfaces.test.mock.visit.MockVisitContextFactory");
+        FactoryFinder.setFactory(FactoryFinder.CLIENT_WINDOW_FACTORY,
+                "org.apache.myfaces.test.mock.MockClientWindowFactory");
+    }
+
+    /**
+     * Setup the <code>lifecycle</code> and <code>lifecycleFactory</code>
+     * variables.
+     * 
+     * @throws Exception
+     */
+    protected void setUpLifecycle()
+    {
+        lifecycleFactory = (MockLifecycleFactory) FactoryFinder
+                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        lifecycle = (MockLifecycle) lifecycleFactory
+                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+    }
+
+    /**
+     * Setup the <code>facesContextFactory</code> and <code>facesContext</code>
+     * variable. Before end, by default it override <code>externalContext</code>
+     * variable from the value retrieved from facesContext.getExternalContext(),
+     * because sometimes it is possible facesContext overrides externalContext
+     * internally.
+     * 
+     * @throws Exception
+     */
+    protected void setUpFacesContext()
+    {
+        facesContextFactory = (MockFacesContextFactory) FactoryFinder
+                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+        facesContext = (MockFacesContext) facesContextFactory.getFacesContext(
+                servletContext, request, response, lifecycle);
+        if (facesContext.getExternalContext() != null)
+        {
+            externalContext = (MockExternalContext) facesContext
+                    .getExternalContext();
+        }
+        else
+        {
+            externalContext = new MockExternalContext(servletContext, request,
+                response);
+            facesContext.setExternalContext(externalContext);
+        }
+        facesContext.setApplication(application);
+    }
+
+    /**
+     * By default, create an instance of UIViewRoot, set its viewId as "/viewId"
+     * and assign it to the current facesContext.
+     * 
+     * @throws Exception
+     */
+    protected void setUpDefaultView()
+    {
+        UIViewRoot root = new UIViewRoot();
+        root.setViewId("/viewId");
+        root.setLocale(getLocale());
+        root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
+        facesContext.setViewRoot(root);
+    }
+    
+    protected Locale getLocale()
+    {
+        return Locale.getDefault();
+    }    
+
+    /**
+     * Setup the <code>application</code> variable and before
+     * the end by default it is assigned to the <code>facesContext</code>
+     * variable, calling <code>facesContext.setApplication(application)</code>
+     * 
+     * @throws Exception
+     */
+    protected void setUpApplication()
+    {
+        ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder
+                .getFactory(FactoryFinder.APPLICATION_FACTORY);
+        application = (MockApplication) applicationFactory.getApplication();
+    }
+
+    /**
+     * Setup the <code>renderKit</code> variable. This is a good place to use
+     * <code>ConfigParser</code> to register converters, validators, components
+     * or renderkits.
+     * 
+     * @throws Exception
+     */
+    protected void setUpRenderKit()
+    {
+        RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
+                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+        renderKit = new MockRenderKit();
+        renderKitFactory.addRenderKit(RenderKitFactory.HTML_BASIC_RENDER_KIT,
+                renderKit);
+    }
+
+    public MockApplication getApplication()
+    {
+        return application;
+    }
+    
+    public MockExternalContext getExternalContext()
+    {
+        return externalContext;
+    }
+
+    public MockFacesContext getFacesContext()
+    {
+        return facesContext;
+    }
+
+    public MockHttpServletRequest getRequest()
+    {
+        return request;
+    }
+
+    public MockHttpServletResponse getResponse()
+    {
+        return response;
+    }
+
+    public MockServletContext getServletContext()
+    {
+        return servletContext;
+    }
+    
+    /**
+     * @return the webContainer
+     */
+    public MockWebContainer getWebContainer()
+    {
+        return webContainer;
+    }
+
+    /**
+     * This method call doRequestDestroyed() and then tearDownRequest(). 
+     */
+    public final void endRequest()
+    {
+        
+        doRequestDestroyed();
+        tearDownRequest();
+    }
+
+    protected void doRequestDestroyed()
+    {
+        if (request != null)
+        {
+            webContainer.requestDestroyed(new ServletRequestEvent(servletContext, request));
+        }
+    }
+
+    protected void tearDownRequest()
+    {
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        externalContext = null;
+        facesContext = null;
+        request = null;
+        response = null;
+    }
+    
+    /**
+     * <p>Tear down instance variables required by this test case.</p>
+     */
+    public void tearDown()
+    {
+        if (facesContext != null)
+        {
+            facesContext.release();
+        }
+        application = null;
+        config = null;
+        externalContext = null;
+        facesContext = null;
+        lifecycle = null;
+        lifecycleFactory = null;
+        renderKit = null;
+        request = null;
+        response = null;
+        servletContext = null;
+        lastSession = null;
+        webContainer = null;
+        FactoryFinder.releaseFactories();
+        ResourceBundleVarNames.resetNames();
+    }
+
+    // ------------------------------------------------------ Instance Variables
+
+    // Mock object instances for our tests
+    protected MockApplication application = null;
+    protected MockServletConfig config = null;
+    protected MockExternalContext externalContext = null;
+    protected MockFacesContext facesContext = null;
+    protected MockFacesContextFactory facesContextFactory = null;
+    protected MockLifecycle lifecycle = null;
+    protected MockLifecycleFactory lifecycleFactory = null;
+    protected MockRenderKit renderKit = null;
+    protected MockHttpServletRequest request = null;
+    protected boolean requestInitializedCalled = false;
+    protected MockHttpServletResponse response = null;
+    protected MockHttpSession lastSession = null;
+    protected MockServletContext servletContext = null;
+    private MockWebContainer webContainer = null;
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_AbstractAttributeMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_AbstractAttributeMap.java
new file mode 100644
index 0000000..ef5aecb
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_AbstractAttributeMap.java
@@ -0,0 +1,448 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.mock;

+

+import java.util.AbstractMap;

+import java.util.AbstractSet;

+import java.util.Collection;

+import java.util.Collections;

+import java.util.Enumeration;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+import java.util.NoSuchElementException;

+import java.util.Set;

+

+/**

+ * Helper Map implementation for use with different Attribute Maps.

+ * 

+ * @author Anton Koinov (latest modification by $Author: jakobk $)

+ * @version $Revision: 979229 $ $Date: 2010-07-26 12:26:53 +0200 (Mo, 26 Jul 2010) $

+ * @since 1.0.0

+ */

+abstract class _AbstractAttributeMap<V> extends AbstractMap<String, V>

+{

+    private Set<String> _keySet;

+    private Collection<V> _values;

+    private Set<Entry<String, V>> _entrySet;

+

+    @Override

+    public void clear()

+    {

+        final List<String> names = Collections.list(getAttributeNames());

+

+        for (String name : names)

+        {

+            removeAttribute(name);

+        }

+    }

+

+    @Override

+    public final boolean containsKey(final Object key)

+    {

+        return getAttribute(key.toString()) != null;

+    }

+

+    @Override

+    public boolean containsValue(final Object findValue)

+    {

+        if (findValue == null)

+        {

+            return false;

+        }

+

+        for (final Enumeration<String> e = getAttributeNames(); e

+                .hasMoreElements();)

+        {

+            final Object value = getAttribute(e.nextElement());

+            if (findValue.equals(value))

+            {

+                return true;

+            }

+        }

+

+        return false;

+    }

+

+    @Override

+    public Set<Entry<String, V>> entrySet()

+    {

+        if (_entrySet == null) 

+        {

+            _entrySet = new EntrySet();

+        }

+        return _entrySet;

+    }

+

+    @Override

+    public V get(final Object key)

+    {

+        return getAttribute(key.toString());

+    }

+

+    @Override

+    public boolean isEmpty()

+    {

+        return !getAttributeNames().hasMoreElements();

+    }

+

+    @Override

+    public Set<String> keySet()

+    {

+        if (_keySet == null)

+        {

+            _keySet = new KeySet();

+        }

+        return _keySet;

+    }

+

+    @Override

+    public final V put(final String key, final V value)

+    {

+        final V retval = getAttribute(key);

+        setAttribute(key, value);

+        return retval;

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends V> t)

+    {

+        for (final Entry<? extends String, ? extends V> entry : t.entrySet())

+        {

+            setAttribute(entry.getKey(), entry.getValue());

+        }

+    }

+

+    @Override

+    public final V remove(final Object key)

+    {

+        final String tempKey = key.toString();

+        final V retval = getAttribute(tempKey);

+        removeAttribute(tempKey);

+        return retval;

+    }

+

+    @Override

+    public int size()

+    {

+        int size = 0;

+        for (final Enumeration<String> e = getAttributeNames(); e

+                .hasMoreElements();)

+        {

+            size++;

+            e.nextElement();

+        }

+        return size;

+    }

+

+    @Override

+    public Collection<V> values()

+    {

+        if(_values == null)

+        {

+            _values = new Values();

+        }

+        return  _values;

+    }

+

+    abstract protected V getAttribute(String key);

+

+    abstract protected void setAttribute(String key, V value);

+

+    abstract protected void removeAttribute(String key);

+

+    abstract protected Enumeration<String> getAttributeNames();

+

+    private abstract class AbstractAttributeSet<E> extends AbstractSet<E>

+    {

+        @Override

+        public boolean isEmpty()

+        {

+            return _AbstractAttributeMap.this.isEmpty();

+        }

+

+        @Override

+        public int size()

+        {

+            return _AbstractAttributeMap.this.size();

+        }

+

+        @Override

+        public void clear()

+        {

+            _AbstractAttributeMap.this.clear();

+        }

+    }

+

+    private final class KeySet extends AbstractAttributeSet<String>

+    {

+        @Override

+        public Iterator<String> iterator()

+        {

+            return new KeyIterator();

+        }

+

+        @Override

+        public boolean contains(final Object o)

+        {

+            return _AbstractAttributeMap.this.containsKey(o);

+        }

+

+        @Override

+        public boolean remove(final Object o)

+        {

+            return _AbstractAttributeMap.this.remove(o) != null;

+        }

+

+    }

+

+    private abstract class AbstractAttributeIterator<E> implements Iterator<E>

+    {

+        // We use a copied version of the Enumeration from getAttributeNames()

+        // here, because directly using it might cause a ConcurrentModificationException

+        // when performing remove(). Note that we can do this since the Enumeration

+        // from getAttributeNames() will contain exactly the attribute names from the time

+        // getAttributeNames() was called and it will not be updated if attributes are 

+        // removed or added.

+        protected final Iterator<String> _i = Collections.list(

+                getAttributeNames()).iterator();

+        protected String _currentKey;

+

+        public void remove()

+        {

+            if (_currentKey == null)

+            {

+                throw new NoSuchElementException(

+                        "You must call next() at least once");

+            }

+            _AbstractAttributeMap.this.remove(_currentKey);

+        }

+

+        public boolean hasNext()

+        {

+            return _i.hasNext();

+        }

+

+        public E next()

+        {

+            _currentKey = _i.next();

+            return getValue(_currentKey);

+        }

+

+        protected abstract E getValue(String attributeName);

+    }

+

+    private final class KeyIterator extends AbstractAttributeIterator<String>

+    {

+        @Override

+        protected String getValue(final String attributeName)

+        {

+            return attributeName;

+        }

+    }

+

+    private class Values extends AbstractAttributeSet<V>

+    {

+        @Override

+        public Iterator<V> iterator()

+        {

+            return new ValuesIterator();

+        }

+

+        @Override

+        public boolean contains(final Object o)

+        {

+            if (o == null)

+            {

+                return false;

+            }

+

+            for (final Iterator<V> it = iterator(); it.hasNext();)

+            {

+                if (o.equals(it.next()))

+                {

+                    return true;

+                }

+            }

+

+            return false;

+        }

+

+        @Override

+        public boolean remove(final Object o)

+        {

+            if (o == null)

+            {

+                return false;

+            }

+

+            for (final Iterator<V> it = iterator(); it.hasNext();)

+            {

+                if (o.equals(it.next()))

+                {

+                    it.remove();

+                    return true;

+                }

+            }

+

+            return false;

+        }

+    }

+

+    private class ValuesIterator extends AbstractAttributeIterator<V>

+    {

+        @Override

+        protected V getValue(final String attributeName)

+        {

+            return _AbstractAttributeMap.this.get(attributeName);

+        }

+    }

+

+    private final class EntrySet extends AbstractAttributeSet<Entry<String, V>>

+    {

+        @Override

+        public Iterator<Entry<String, V>> iterator()

+        {

+            return new EntryIterator();

+        }

+

+        @SuppressWarnings("unchecked")

+        @Override

+        public boolean contains(final Object o)

+        {

+            if (!(o instanceof Entry))

+            {

+                return false;

+            }

+

+            final Entry<String, V> entry = (Entry<String, V>) o;

+            final Object key = entry.getKey();

+            final Object value = entry.getValue();

+            if (key == null || value == null)

+            {

+                return false;

+            }

+

+            return value.equals(_AbstractAttributeMap.this.get(key));

+        }

+

+        @SuppressWarnings("unchecked")

+        @Override

+        public boolean remove(final Object o)

+        {

+            if (!(o instanceof Entry))

+            {

+                return false;

+            }

+

+            final Entry<String, V> entry = (Entry<String, V>) o;

+            final Object key = entry.getKey();

+            final Object value = entry.getValue();

+            if (key == null || value == null

+                    || !value.equals(_AbstractAttributeMap.this.get(key)))

+            {

+                return false;

+            }

+

+            return _AbstractAttributeMap.this.remove(((Entry<String, V>) o)

+                    .getKey()) != null;

+        }

+    }

+

+    /**

+     * Not very efficient since it generates a new instance of <code>Entry</code> for each element and still internaly

+     * uses the <code>KeyIterator</code>. It is more efficient to use the <code>KeyIterator</code> directly.

+     */

+    private final class EntryIterator extends

+            AbstractAttributeIterator<Entry<String, V>>

+    {

+        @Override

+        protected Entry<String, V> getValue(final String attributeName)

+        {

+            // Must create new Entry every time--value of the entry must stay

+            // linked to the same attribute name

+            return new EntrySetEntry(attributeName);

+        }

+    }

+

+    private final class EntrySetEntry implements Entry<String, V>

+    {

+        private final String _currentKey;

+

+        public EntrySetEntry(final String currentKey)

+        {

+            _currentKey = currentKey;

+        }

+

+        public String getKey()

+        {

+            return _currentKey;

+        }

+

+        public V getValue()

+        {

+            return _AbstractAttributeMap.this.get(_currentKey);

+        }

+

+        public V setValue(final V value)

+        {

+            return _AbstractAttributeMap.this.put(_currentKey, value);

+        }

+

+        @Override

+        public int hashCode()

+        {

+            int result = 1;

+            result = 31 * result

+                    + ((_currentKey == null) ? 0 : _currentKey.hashCode());

+            return result;

+        }

+

+        @Override

+        public boolean equals(final Object obj)

+        {

+            if (this == obj)

+            {

+                return true;

+            }

+            if (obj == null)

+            {

+                return false;

+            }

+            if (getClass() != obj.getClass())

+            {

+                return false;

+            }

+            final EntrySetEntry other = (EntrySetEntry) obj;

+            if (_currentKey == null)

+            {

+                if (other._currentKey != null)

+                {

+                    return false;

+                }

+            }

+            else if (!_currentKey.equals(other._currentKey))

+            {

+                return false;

+            }

+            return true;

+        }

+

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_ApplicationMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_ApplicationMap.java
new file mode 100644
index 0000000..8f4088e
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_ApplicationMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+

+import javax.servlet.ServletContext;

+

+/**

+ * ServletContext attributes as a Map.

+ *

+ * @author Anton Koinov (latest modification by $Author: slessard $)

+ * @version $Revision: 701829 $ $Date: 2008-10-05 19:06:02 +0200 (So, 05 Okt 2008) $

+ * @since 1.0.0

+ */

+final class _ApplicationMap extends _AbstractAttributeMap<Object>

+{

+    final ServletContext _servletContext;

+

+    _ApplicationMap(final ServletContext servletContext)

+    {

+        _servletContext = servletContext;

+    }

+

+    @Override

+    protected Object getAttribute(final String key)

+    {

+        return _servletContext.getAttribute(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final Object value)

+    {

+        _servletContext.setAttribute(key, value);

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        _servletContext.removeAttribute(key);

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _servletContext.getAttributeNames();

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends Object> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public void clear()

+    {

+        throw new UnsupportedOperationException();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_CookieMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_CookieMap.java
new file mode 100644
index 0000000..9b46d2b
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_CookieMap.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 org.apache.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+import java.util.NoSuchElementException;

+

+import javax.servlet.http.Cookie;

+import javax.servlet.http.HttpServletRequest;

+

+/**

+ * HttpServletRequest Cookies as Map.

+ *

+ * @author Dimitry D'hondt

+ * @author Anton Koinov

+ * @version $Revision: 701829 $ $Date: 2008-10-05 19:06:02 +0200 (So, 05 Okt 2008) $

+ * @since 1.0.0

+ */

+final class _CookieMap extends _AbstractAttributeMap<Object>

+{

+    private static final Cookie[] EMPTY_ARRAY = new Cookie[0];

+

+    private final HttpServletRequest _httpServletRequest;

+

+    _CookieMap(final HttpServletRequest httpServletRequest)

+    {

+        _httpServletRequest = httpServletRequest;

+    }

+

+    @Override

+    public void clear()

+    {

+        throw new UnsupportedOperationException(

+                "Cannot clear HttpRequest Cookies");

+    }

+

+    @Override

+    public boolean containsValue(final Object findValue)

+    {

+        if (findValue == null)

+        {

+            return false;

+        }

+

+        final Cookie[] cookies = _httpServletRequest.getCookies();

+        if (cookies == null)

+        {

+            return false;

+        }

+        for (int i = 0, len = cookies.length; i < len; i++)

+        {

+            if (findValue.equals(cookies[i]))

+            {

+                return true;

+            }

+        }

+

+        return false;

+    }

+

+    @Override

+    public boolean isEmpty()

+    {

+        final Cookie[] cookies = _httpServletRequest.getCookies();

+        return cookies == null || cookies.length == 0;

+    }

+

+    @Override

+    public int size()

+    {

+        final Cookie[] cookies = _httpServletRequest.getCookies();

+        return cookies == null ? 0 : cookies.length;

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends Object> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    protected Object getAttribute(final String key)

+    {

+        final Cookie[] cookies = _httpServletRequest.getCookies();

+        if (cookies == null)

+        {

+            return null;

+        }

+        for (int i = 0, len = cookies.length; i < len; i++)

+        {

+            if (cookies[i].getName().equals(key))

+            {

+                return cookies[i];

+            }

+        }

+

+        return null;

+    }

+

+    @Override

+    protected void setAttribute(final String key, final Object value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set HttpRequest Cookies");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove HttpRequest Cookies");

+    }

+

+    @Override

+    protected Enumeration<String> getAttributeNames()

+    {

+        final Cookie[] cookies = _httpServletRequest.getCookies();

+

+        return cookies == null ? new CookieNameEnumeration(EMPTY_ARRAY)

+                : new CookieNameEnumeration(cookies);

+

+    }

+

+    private static class CookieNameEnumeration implements Enumeration<String>

+    {

+        private final Cookie[] _cookies;

+        private final int _length;

+        private int _index;

+

+        public CookieNameEnumeration(final Cookie[] cookies)

+        {

+            _cookies = cookies;

+            _length = cookies.length;

+        }

+

+        public boolean hasMoreElements()

+        {

+            return _index < _length;

+        }

+

+        public String nextElement()

+        {

+            if (!hasMoreElements())

+            {

+                throw new NoSuchElementException();

+            }

+            return _cookies[_index++].getName();

+        }

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_ELText.java b/test30/src/main/java/org/apache/myfaces/test/mock/_ELText.java
new file mode 100644
index 0000000..6371c13
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_ELText.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.myfaces.test.mock;

+

+import java.io.IOException;

+import java.io.Writer;

+import java.util.ArrayList;

+import java.util.List;

+

+import javax.el.ELContext;

+import javax.el.ELException;

+import javax.el.ExpressionFactory;

+import javax.el.ValueExpression;

+import javax.faces.context.ResponseWriter;

+

+/**

+ * Handles parsing EL Strings in accordance with the EL-API Specification. The parser accepts either <code>${..}</code>

+ * or <code>#{..}</code>.

+ * 

+ * @author Jacob Hookom

+ * @version $Id: ELText.java,v 1.8 2008/07/13 19:01:42 rlubke Exp $

+ */

+class _ELText

+{

+

+    private static final class LiteralValueExpression extends ValueExpression

+    {

+

+        /**

+         * 

+         */

+        private static final long serialVersionUID = 1L;

+

+        private final String text;

+

+        public LiteralValueExpression(String text)

+        {

+            this.text = text;

+        }

+

+        public boolean isLiteralText()

+        {

+            return false;

+        }

+

+        public int hashCode()

+        {

+            return 0;

+        }

+

+        public String getExpressionString()

+        {

+            return this.text;

+        }

+

+        public boolean equals(Object obj)

+        {

+            return false;

+        }

+

+        public void setValue(ELContext context, Object value)

+        {

+        }

+

+        public boolean isReadOnly(ELContext context)

+        {

+            return false;

+        }

+

+        public Object getValue(ELContext context)

+        {

+            return null;

+        }

+

+        public Class<?> getType(ELContext context)

+        {

+            return null;

+        }

+

+        public Class<?> getExpectedType()

+        {

+            return null;

+        }

+

+    }

+

+    private static final class ELTextComposite extends _ELText

+    {

+        private final _ELText[] txt;

+

+        public ELTextComposite(_ELText[] txt)

+        {

+            super(null);

+            this.txt = txt;

+        }

+

+        public void write(Writer out, ELContext ctx) throws ELException, IOException

+        {

+            for (int i = 0; i < this.txt.length; i++)

+            {

+                this.txt[i].write(out, ctx);

+            }

+        }

+

+        public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException

+        {

+            for (int i = 0; i < this.txt.length; i++)

+            {

+                this.txt[i].writeText(out, ctx);

+            }

+        }

+

+        public String toString(ELContext ctx)

+        {

+            StringBuffer sb = new StringBuffer();

+            for (int i = 0; i < this.txt.length; i++)

+            {

+                sb.append(this.txt[i].toString(ctx));

+            }

+            return sb.toString();

+        }

+

+        /*

+         * public String toString(ELContext ctx) { StringBuffer sb = new StringBuffer(); for (int i = 0; i <

+         * this.txt.length; i++) { sb.append(this.txt[i].toString(ctx)); } return sb.toString(); }

+         */

+

+        public String toString()

+        {

+            StringBuffer sb = new StringBuffer();

+            for (int i = 0; i < this.txt.length; i++)

+            {

+                sb.append(this.txt[i].toString());

+            }

+            return sb.toString();

+        }

+

+        public boolean isLiteral()

+        {

+            return false;

+        }

+

+        public _ELText apply(ExpressionFactory factory, ELContext ctx)

+        {

+            int len = this.txt.length;

+            _ELText[] nt = new _ELText[len];

+            for (int i = 0; i < len; i++)

+            {

+                nt[i] = this.txt[i].apply(factory, ctx);

+            }

+            return new ELTextComposite(nt);

+        }

+    }

+

+    private static final class ELTextVariable extends _ELText

+    {

+        private final ValueExpression ve;

+

+        public ELTextVariable(ValueExpression ve)

+        {

+            super(ve.getExpressionString());

+            this.ve = ve;

+        }

+

+        public boolean isLiteral()

+        {

+            return false;

+        }

+

+        public _ELText apply(ExpressionFactory factory, ELContext ctx)

+        {

+            return new ELTextVariable(factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class));

+        }

+

+        public void write(Writer out, ELContext ctx) throws ELException, IOException

+        {

+            Object v = this.ve.getValue(ctx);

+            if (v != null)

+            {

+                out.write((String) v);

+            }

+        }

+

+        public String toString(ELContext ctx) throws ELException

+        {

+            Object v = this.ve.getValue(ctx);

+            if (v != null)

+            {

+                return v.toString();

+            }

+

+            return null;

+        }

+

+        public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException

+        {

+            Object v = this.ve.getValue(ctx);

+            if (v != null)

+            {

+                out.writeText((String) v, null);

+            }

+        }

+    }

+

+    protected final String literal;

+

+    public _ELText(String literal)

+    {

+        this.literal = literal;

+    }

+

+    /**

+     * If it's literal text

+     * 

+     * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)

+     */

+    public boolean isLiteral()

+    {

+        return true;

+    }

+

+    /**

+     * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.

+     * 

+     * @param factory

+     *            the ExpressionFactory to use

+     * @param ctx

+     *            the ELContext to use

+     * @return an ELText instance

+     */

+    public _ELText apply(ExpressionFactory factory, ELContext ctx)

+    {

+        return this;

+    }

+

+    /**

+     * Allow this instance to write to the passed Writer, given the ELContext state

+     * 

+     * @param out

+     *            Writer to write to

+     * @param ctx

+     *            current ELContext state

+     * @throws ELException

+     * @throws IOException

+     */

+    public void write(Writer out, ELContext ctx) throws ELException, IOException

+    {

+        out.write(this.literal);

+    }

+

+    public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException

+    {

+        out.writeText(this.literal, null);

+    }

+

+    /**

+     * Evaluates the ELText to a String

+     * 

+     * @param ctx

+     *            current ELContext state

+     * @throws ELException

+     * @return the evaluated String

+     */

+    public String toString(ELContext ctx) throws ELException

+    {

+        return this.literal;

+    }

+

+    public String toString()

+    {

+        return this.literal;

+    }

+

+    /**

+     * Parses the passed string to determine if it's literal or not

+     * 

+     * @param in

+     *            input String

+     * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)

+     */

+    public static boolean isLiteral(String in)

+    {

+        _ELText txt = parse(in);

+        return txt == null || txt.isLiteral();

+    }

+

+    /**

+     * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are

+     * treated as {@link org.apache.myfaces.view.facelets.el.LiteralValueExpression LiteralValueExpressions}.

+     * 

+     * @param in

+     *            String to parse

+     * @return ELText instance that knows if the String was literal or not

+     * @throws javax.el.ELException

+     */

+    public static _ELText parse(String in) throws ELException

+    {

+        return parse(null, null, in);

+    }

+

+    /**

+     * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the

+     * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and

+     * properties will not be evaluated.

+     * 

+     * @param fact

+     *            ExpressionFactory to use

+     * @param ctx

+     *            ELContext to validate against

+     * @param in

+     *            String to parse

+     * @return ELText that can be re-applied later

+     * @throws javax.el.ELException

+     */

+    public static _ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException

+    {

+        char[] ca = in.toCharArray();

+        int i = 0;

+        char c = 0;

+        int len = ca.length;

+        int end = len - 1;

+        boolean esc = false;

+        int vlen = 0;

+

+        StringBuffer buff = new StringBuffer(128);

+        List<_ELText> text = new ArrayList<_ELText>();

+        _ELText t = null;

+        ValueExpression ve = null;

+

+        while (i < len)

+        {

+            c = ca[i];

+            if ('\\' == c)

+            {

+                esc = !esc;

+                if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))

+                {

+                    i++;

+                    continue;

+                }

+            }

+            else if (!esc && ('$' == c || '#' == c))

+            {

+                if (i < end)

+                {

+                    if ('{' == ca[i + 1])

+                    {

+                        if (buff.length() > 0)

+                        {

+                            text.add(new _ELText(buff.toString()));

+                            buff.setLength(0);

+                        }

+                        vlen = findVarLength(ca, i);

+                        if (ctx != null && fact != null)

+                        {

+                            ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);

+                            t = new ELTextVariable(ve);

+                        }

+                        else

+                        {

+                            t = new ELTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));

+                        }

+                        text.add(t);

+                        i += vlen;

+                        continue;

+                    }

+                }

+            }

+            esc = false;

+            buff.append(c);

+            i++;

+        }

+

+        if (buff.length() > 0)

+        {

+            text.add(new _ELText(new String(buff.toString())));

+            buff.setLength(0);

+        }

+

+        if (text.size() == 0)

+        {

+            return null;

+        }

+        else if (text.size() == 1)

+        {

+            return (_ELText) text.get(0);

+        }

+        else

+        {

+            _ELText[] ta = (_ELText[]) text.toArray(new _ELText[text.size()]);

+            return new ELTextComposite(ta);

+        }

+    }

+

+    private static int findVarLength(char[] ca, int s) throws ELException

+    {

+        int i = s;

+        int len = ca.length;

+        char c = 0;

+        int str = 0;

+        while (i < len)

+        {

+            c = ca[i];

+            if ('\\' == c && i < len - 1)

+            {

+                i++;

+            }

+            else if ('\'' == c || '"' == c)

+            {

+                if (str == c)

+                {

+                    str = 0;

+                }

+                else

+                {

+                    str = c;

+                }

+            }

+            else if (str == 0 && ('}' == c))

+            {

+                return i - s + 1;

+            }

+            i++;

+        }

+        throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_Hex.java b/test30/src/main/java/org/apache/myfaces/test/mock/_Hex.java
new file mode 100644
index 0000000..b22a80e
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_Hex.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.myfaces.test.mock;

+

+import java.io.UnsupportedEncodingException;

+

+/**

+ * Hex encoder and decoder. The charset used for certain operation can be set, the default is set in

+ * {@link #DEFAULT_CHARSET_NAME}

+ * 

+ * @since 1.0.0

+ * @author Apache Software Foundation

+ * @version $Id: Hex.java 801639 2009-08-06 13:15:10Z niallp $

+ */

+class _Hex

+{

+

+    /**

+     * Default charset name is {@link CharEncoding#UTF_8}

+     */

+    public static final String DEFAULT_CHARSET_NAME = "UTF-8";

+

+    /**

+     * Used to build output as Hex

+     */

+    private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5',

+            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

+

+    /**

+     * Used to build output as Hex

+     */

+    private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5',

+            '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

+

+    /**

+     * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The

+     * returned array will be half the length of the passed array, as it takes two characters to represent any given

+     * byte. An exception is thrown if the passed char array has an odd number of elements.

+     * 

+     * @param data

+     *            An array of characters containing hexadecimal digits

+     * @return A byte array containing binary data decoded from the supplied char array.

+     * @throws DecoderException

+     *             Thrown if an odd number or illegal of characters is supplied

+     */

+    public static byte[] decodeHex(char[] data) throws Exception

+    {

+

+        int len = data.length;

+

+        if ((len & 0x01) != 0)

+        {

+            throw new Exception("Odd number of characters.");

+        }

+

+        byte[] out = new byte[len >> 1];

+

+        // two characters form the hex value.

+        for (int i = 0, j = 0; j < len; i++)

+        {

+            int f = toDigit(data[j], j) << 4;

+            j++;

+            f = f | toDigit(data[j], j);

+            j++;

+            out[i] = (byte) (f & 0xFF);

+        }

+

+        return out;

+    }

+

+    /**

+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.

+     * The returned array will be double the length of the passed array, as it takes two characters to represent any

+     * given byte.

+     * 

+     * @param data

+     *            a byte[] to convert to Hex characters

+     * @return A char[] containing hexadecimal characters

+     */

+    public static char[] encodeHex(byte[] data)

+    {

+        return encodeHex(data, true);

+    }

+

+    /**

+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.

+     * The returned array will be double the length of the passed array, as it takes two characters to represent any

+     * given byte.

+     * 

+     * @param data

+     *            a byte[] to convert to Hex characters

+     * @param toLowerCase

+     *            <code>true</code> converts to lowercase, <code>false</code> to uppercase

+     * @return A char[] containing hexadecimal characters

+     */

+    public static char[] encodeHex(byte[] data, boolean toLowerCase)

+    {

+        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);

+    }

+

+    /**

+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.

+     * The returned array will be double the length of the passed array, as it takes two characters to represent any

+     * given byte.

+     * 

+     * @param data

+     *            a byte[] to convert to Hex characters

+     * @param toDigits

+     *            the output alphabet

+     * @return A char[] containing hexadecimal characters

+     */

+    protected static char[] encodeHex(byte[] data, char[] toDigits)

+    {

+        int l = data.length;

+        char[] out = new char[l << 1];

+        // two characters form the hex value.

+        for (int i = 0, j = 0; i < l; i++)

+        {

+            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];

+            out[j++] = toDigits[0x0F & data[i]];

+        }

+        return out;

+    }

+

+    /**

+     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned

+     * String will be double the length of the passed array, as it takes two characters to represent any given byte.

+     * 

+     * @param data

+     *            a byte[] to convert to Hex characters

+     * @return A String containing hexadecimal characters

+     */

+    public static String encodeHexString(byte[] data)

+    {

+        return new String(encodeHex(data));

+    }

+

+    /**

+     * Converts a hexadecimal character to an integer.

+     * 

+     * @param ch

+     *            A character to convert to an integer digit

+     * @param index

+     *            The index of the character in the source

+     * @return An integer

+     * @throws DecoderException

+     *             Thrown if ch is an illegal hex character

+     */

+    protected static int toDigit(char ch, int index) throws Exception

+    {

+        int digit = Character.digit(ch, 16);

+        if (digit == -1)

+        {

+            throw new Exception("Illegal hexadecimal charcter " + ch

+                    + " at index " + index);

+        }

+        return digit;

+    }

+

+    private final String charsetName;

+

+    /**

+     * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET_NAME}

+     */

+    public _Hex()

+    {

+        // use default encoding

+        this.charsetName = DEFAULT_CHARSET_NAME;

+    }

+

+    /**

+     * Creates a new codec with the given charset name.

+     * 

+     * @param csName

+     *            the charset name.

+     */

+    public _Hex(String csName)

+    {

+        this.charsetName = csName;

+    }

+

+    /**

+     * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.

+     * The returned array will be half the length of the passed array, as it takes two characters to represent any given

+     * byte. An exception is thrown if the passed char array has an odd number of elements.

+     * 

+     * @param array

+     *            An array of character bytes containing hexadecimal digits

+     * @return A byte array containing binary data decoded from the supplied byte array (representing characters).

+     * @throws DecoderException

+     *             Thrown if an odd number of characters is supplied to this function

+     * @see #decodeHex(char[])

+     */

+    public byte[] decode(byte[] array) throws Exception

+    {

+        try

+        {

+            return decodeHex(new String(array, getCharsetName()).toCharArray());

+        }

+        catch (UnsupportedEncodingException e)

+        {

+            throw new Exception(e.getMessage(), e);

+        }

+    }

+

+    /**

+     * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those

+     * same values. The returned array will be half the length of the passed String or array, as it takes two characters

+     * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.

+     * 

+     * @param object

+     *            A String or, an array of character bytes containing hexadecimal digits

+     * @return A byte array containing binary data decoded from the supplied byte array (representing characters).

+     * @throws DecoderException

+     *             Thrown if an odd number of characters is supplied to this function or the object is not a String or

+     *             char[]

+     * @see #decodeHex(char[])

+     */

+    public Object decode(Object object) throws Exception

+    {

+        try

+        {

+            char[] charArray = object instanceof String ? ((String) object)

+                    .toCharArray() : (char[]) object;

+            return decodeHex(charArray);

+        }

+        catch (ClassCastException e)

+        {

+            throw new Exception(e.getMessage(), e);

+        }

+    }

+

+    /**

+     * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each

+     * byte in order. The returned array will be double the length of the passed array, as it takes two characters to

+     * represent any given byte.

+     * <p>

+     * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by

+     * {@link #getCharsetName()}.

+     * </p>

+     * 

+     * @param array

+     *            a byte[] to convert to Hex characters

+     * @return A byte[] containing the bytes of the hexadecimal characters

+     * @throws IllegalStateException

+     *             if the charsetName is invalid. This API throws {@link IllegalStateException} instead of

+     *             {@link UnsupportedEncodingException} for backward compatibility.

+     * @see #encodeHex(byte[])

+     */

+    public byte[] encode(byte[] array)

+    {

+        return getBytesUnchecked(encodeHexString(array), getCharsetName());

+    }

+

+    private static byte[] getBytesUnchecked(String string, String charsetName)

+    {

+        if (string == null)

+        {

+            return null;

+        }

+        try

+        {

+            return string.getBytes(charsetName);

+        }

+        catch (UnsupportedEncodingException e)

+        {

+            throw new IllegalStateException(charsetName, e);

+        }

+    }

+

+    /**

+     * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each

+     * byte in order. The returned array will be double the length of the passed String or array, as it takes two

+     * characters to represent any given byte.

+     * <p>

+     * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by

+     * {@link #getCharsetName()}.

+     * </p>

+     * 

+     * @param object

+     *            a String, or byte[] to convert to Hex characters

+     * @return A char[] containing hexadecimal characters

+     * @throws EncoderException

+     *             Thrown if the given object is not a String or byte[]

+     * @see #encodeHex(byte[])

+     */

+    public Object encode(Object object) throws Exception

+    {

+        try

+        {

+            byte[] byteArray = object instanceof String ? ((String) object)

+                    .getBytes(getCharsetName()) : (byte[]) object;

+            return encodeHex(byteArray);

+        }

+        catch (ClassCastException e)

+        {

+            throw new Exception(e.getMessage(), e);

+        }

+        catch (UnsupportedEncodingException e)

+        {

+            throw new Exception(e.getMessage(), e);

+        }

+    }

+

+    /**

+     * Gets the charset name.

+     * 

+     * @return the charset name.

+     */

+    public String getCharsetName()

+    {

+        return this.charsetName;

+    }

+

+    /**

+     * Returns a string representation of the object, which includes the charset name.

+     * 

+     * @return a string representation of the object.

+     */

+    public String toString()

+    {

+        return super.toString() + "[charsetName=" + this.charsetName + "]";

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_InitParameterMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_InitParameterMap.java
new file mode 100644
index 0000000..5cd02c5
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_InitParameterMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+

+import javax.servlet.ServletContext;

+

+/**

+ * ServletContext init parameters as Map.

+ * 

+ * @author Anton Koinov (latest modification by $Author: slessard $)

+ * @version $Revision: 701829 $ $Date: 2008-10-05 19:06:02 +0200 (So, 05 Okt 2008) $

+ * @since 1.0.0

+ */

+final class _InitParameterMap extends _AbstractAttributeMap<String>

+{

+    private final ServletContext _servletContext;

+

+    _InitParameterMap(final ServletContext servletContext)

+    {

+        _servletContext = servletContext;

+    }

+

+    @Override

+    protected String getAttribute(final String key)

+    {

+        return _servletContext.getInitParameter(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final String value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set ServletContext InitParameter");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove ServletContext InitParameter");

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _servletContext.getInitParameterNames();

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends String> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public void clear()

+    {

+        throw new UnsupportedOperationException();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_NullEnumeration.java b/test30/src/main/java/org/apache/myfaces/test/mock/_NullEnumeration.java
new file mode 100644
index 0000000..4ff90b5
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_NullEnumeration.java
@@ -0,0 +1,49 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.NoSuchElementException;

+

+/**

+ * Enumeration without elements

+ *

+ * @author Anton Koinov (latest modification by $Author: matzew $)

+ * @version $Revision: 557350 $ $Date: 2007-07-18 13:19:50 -0500 (Mié, 18 Jul 2007) $

+ * @since 1.0.0

+ */

+final class _NullEnumeration implements Enumeration

+{

+    private static final _NullEnumeration NULL_ENUMERATION = new _NullEnumeration();

+

+    public static final _NullEnumeration instance()

+    {

+        return NULL_ENUMERATION;

+    }

+

+    public boolean hasMoreElements()

+    {

+        return false;

+    }

+

+    public Object nextElement()

+    {

+        throw new NoSuchElementException("NullEnumeration has no elements");

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderMap.java
new file mode 100644
index 0000000..90466c1
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+

+import javax.servlet.http.HttpServletRequest;

+

+/**

+ * HttpServletRequest headers as Map.

+ * 

+ * @author Anton Koinov (latest modification by $Author: slessard $)

+ * @version $Revision: 698799 $ $Date: 2008-09-25 04:03:47 +0200 (Do, 25 Sep 2008) $

+ * @since 1.0.0

+ */

+final class _RequestHeaderMap extends _AbstractAttributeMap<String>

+{

+    private final HttpServletRequest _httpServletRequest;

+

+    _RequestHeaderMap(final HttpServletRequest httpServletRequest)

+    {

+        _httpServletRequest = httpServletRequest;

+    }

+

+    @Override

+    protected String getAttribute(final String key)

+    {

+        return _httpServletRequest.getHeader(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final String value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set HttpServletRequest Header");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove HttpServletRequest Header");

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _httpServletRequest.getHeaderNames();

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends String> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public void clear()

+    {

+        throw new UnsupportedOperationException();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderValuesMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderValuesMap.java
new file mode 100644
index 0000000..2a60140
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestHeaderValuesMap.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.myfaces.test.mock;

+

+import java.util.ArrayList;

+import java.util.Enumeration;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+import javax.servlet.http.HttpServletRequest;

+

+/**

+ * HttpServletRequest header values (multi-value headers) as Map of String[].

+ * 

+ * @author Anton Koinov (latest modification by $Author: slessard $)

+ * @version $Revision: 701829 $ $Date: 2008-10-05 19:06:02 +0200 (So, 05 Okt 2008) $

+ * @since 1.0.0

+ */

+final class _RequestHeaderValuesMap extends _AbstractAttributeMap<String[]>

+{

+    private final HttpServletRequest _httpServletRequest;

+    private final Map<String, String[]> _valueCache = new HashMap<String, String[]>();

+

+    _RequestHeaderValuesMap(final HttpServletRequest httpServletRequest)

+    {

+        _httpServletRequest = httpServletRequest;

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected String[] getAttribute(final String key)

+    {

+        String[] ret = _valueCache.get(key);

+        if (ret == null)

+        {

+            ret = toArray(_httpServletRequest

+                    .getHeaders(key));

+            _valueCache.put(key, ret);

+        }

+

+        return ret;

+    }

+

+    @Override

+    protected void setAttribute(final String key, final String[] value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set HttpServletRequest HeaderValues");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove HttpServletRequest HeaderValues");

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _httpServletRequest.getHeaderNames();

+    }

+

+    private String[] toArray(Enumeration<String> e)

+    {

+        List<String> ret = new ArrayList<String>();

+

+        while (e.hasMoreElements())

+        {

+            ret.add(e.nextElement());

+        }

+

+        return ret.toArray(new String[ret.size()]);

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_RequestMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestMap.java
new file mode 100644
index 0000000..366b689
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+

+import javax.servlet.ServletRequest;

+

+/**

+ * ServletRequest attributes Map.

+ * 

+ * @author Anton Koinov (latest modification by $Author: slessard $)

+ * @version $Revision: 698799 $ $Date: 2008-09-25 04:03:47 +0200 (Do, 25 Sep 2008) $

+ * @since 1.0.0

+ */

+final class _RequestMap extends _AbstractAttributeMap<Object>

+{

+    final ServletRequest _servletRequest;

+

+    _RequestMap(final ServletRequest servletRequest)

+    {

+        _servletRequest = servletRequest;

+    }

+

+    @Override

+    protected Object getAttribute(final String key)

+    {

+        return _servletRequest.getAttribute(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final Object value)

+    {

+        _servletRequest.setAttribute(key, value);

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        _servletRequest.removeAttribute(key);

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _servletRequest.getAttributeNames();

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends Object> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public void clear()

+    {

+        throw new UnsupportedOperationException();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterMap.java
new file mode 100644
index 0000000..2eecb47
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+

+import javax.servlet.ServletRequest;

+

+/**

+ * ServletRequest parameters as Map.

+ * 

+ * @author Anton Koinov (latest modification by $Author: lu4242 $)

+ * @version $Revision: 695059 $ $Date: 2008-09-14 01:10:53 +0200 (So, 14 Sep 2008) $

+ * @since 1.0.0

+ */

+final class _RequestParameterMap extends _AbstractAttributeMap<String>

+{

+    private final ServletRequest _servletRequest;

+

+    _RequestParameterMap(final ServletRequest servletRequest)

+    {

+        _servletRequest = servletRequest;

+    }

+

+    @Override

+    protected String getAttribute(final String key)

+    {

+        return _servletRequest.getParameter(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final String value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set ServletRequest Parameter");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove ServletRequest Parameter");

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _servletRequest.getParameterNames();

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterValuesMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterValuesMap.java
new file mode 100644
index 0000000..fa34704
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_RequestParameterValuesMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+

+import javax.servlet.ServletRequest;

+

+/**

+ * ServletRequest multi-value parameters as Map.

+ * 

+ * @author Anton Koinov (latest modification by $Author: lu4242 $)

+ * @version $Revision: 695059 $ $Date: 2008-09-14 01:10:53 +0200 (So, 14 Sep 2008) $

+ * @since 1.0.0

+ */

+final class _RequestParameterValuesMap extends _AbstractAttributeMap<String[]>

+{

+    private final ServletRequest _servletRequest;

+

+    _RequestParameterValuesMap(final ServletRequest servletRequest)

+    {

+        _servletRequest = servletRequest;

+    }

+

+    @Override

+    protected String[] getAttribute(final String key)

+    {

+        return _servletRequest.getParameterValues(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final String[] value)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot set ServletRequest ParameterValues");

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        throw new UnsupportedOperationException(

+                "Cannot remove ServletRequest ParameterValues");

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        return _servletRequest.getParameterNames();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/_SessionMap.java b/test30/src/main/java/org/apache/myfaces/test/mock/_SessionMap.java
new file mode 100644
index 0000000..64f2f47
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/_SessionMap.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.myfaces.test.mock;

+

+import java.util.Enumeration;

+import java.util.Map;

+

+import javax.servlet.http.HttpServletRequest;

+import javax.servlet.http.HttpSession;

+

+/**

+ * HttpSession attibutes as Map.

+ *

+ * @author Anton Koinov (latest modification by $Author: jakobk $)

+ * @version $Revision: 979229 $ $Date: 2010-07-26 12:26:53 +0200 (Mo, 26 Jul 2010) $

+ * @since 1.0.0

+ */

+final class _SessionMap extends _AbstractAttributeMap<Object>

+{

+    private final HttpServletRequest _httpRequest;

+

+    _SessionMap(final HttpServletRequest httpRequest)

+    {

+        _httpRequest = httpRequest;

+    }

+

+    @Override

+    protected Object getAttribute(final String key)

+    {

+        final HttpSession httpSession = _getSession();

+        return (httpSession == null) ? null : httpSession.getAttribute(key);

+    }

+

+    @Override

+    protected void setAttribute(final String key, final Object value)

+    {

+        _httpRequest.getSession(true).setAttribute(key, value);

+    }

+

+    @Override

+    protected void removeAttribute(final String key)

+    {

+        final HttpSession httpSession = _getSession();

+        if (httpSession != null)

+        {

+            httpSession.removeAttribute(key);

+        }

+    }

+

+    @Override

+    @SuppressWarnings("unchecked")

+    protected Enumeration<String> getAttributeNames()

+    {

+        final HttpSession httpSession = _getSession();

+        return (httpSession == null) ? _NullEnumeration.instance()

+                : httpSession.getAttributeNames();

+    }

+

+    @Override

+    public void putAll(final Map<? extends String, ? extends Object> t)

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    // we can use public void clear() from super-class

+

+    private HttpSession _getSession()

+    {

+        return _httpRequest.getSession(false);

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ApplyRequestValuesExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ApplyRequestValuesExecutor.java
new file mode 100644
index 0000000..ae803ea
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ApplyRequestValuesExecutor.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.myfaces.test.mock.lifecycle;

+

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2

+ *

+ * Apply request values phase (JSF Spec 2.2.2)

+ * 

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ */

+class ApplyRequestValuesExecutor implements PhaseExecutor

+{

+    public boolean execute(FacesContext facesContext)

+    {

+        facesContext.getViewRoot().processDecodes(facesContext);

+        return false;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.APPLY_REQUEST_VALUES;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/DefaultRestoreViewSupport.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/DefaultRestoreViewSupport.java
new file mode 100644
index 0000000..5158189
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/DefaultRestoreViewSupport.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.myfaces.test.mock.lifecycle;

+

+import java.util.Iterator;

+import java.util.Map;

+

+import javax.el.ValueExpression;

+import javax.faces.FacesException;

+import javax.faces.component.UIComponent;

+import javax.faces.context.ExternalContext;

+import javax.faces.context.FacesContext;

+

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+

+/**

+ * @author Mathias Broekelmann (latest modification by $Author: mbr $)

+ * @version $Revision: 517403 $ $Date: 2007-03-12 22:17:00 +0100 (Mo, 12 Mrz 2007) $

+ * @since 1.0.0

+ */

+public class DefaultRestoreViewSupport implements RestoreViewSupport

+{

+    private static final String JAVAX_SERVLET_INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";

+

+    private static final String JAVAX_SERVLET_INCLUDE_PATH_INFO = "javax.servlet.include.path_info";

+

+    private final Log log = LogFactory.getLog(DefaultRestoreViewSupport.class);

+

+    public void processComponentBinding(FacesContext facesContext,

+            UIComponent component)

+    {

+        ValueExpression binding = component.getValueExpression("binding");

+        if (binding != null)

+        {

+            binding.setValue(facesContext.getELContext(), component);

+        }

+

+        for (Iterator iter = component.getFacetsAndChildren(); iter.hasNext();)

+        {

+            processComponentBinding(facesContext, (UIComponent) iter.next());

+        }

+    }

+

+    public String calculateViewId(FacesContext facesContext)

+    {

+        //Assert.notNull(facesContext);

+        ExternalContext externalContext = facesContext.getExternalContext();

+        Map requestMap = externalContext.getRequestMap();

+

+        String viewId = (String) requestMap

+                .get(JAVAX_SERVLET_INCLUDE_PATH_INFO);

+        boolean traceEnabled = log.isTraceEnabled();

+        if (viewId != null)

+        {

+            if (traceEnabled)

+            {

+                log.trace("Calculated viewId '" + viewId

+                        + "' from request param '"

+                        + JAVAX_SERVLET_INCLUDE_PATH_INFO + "'");

+            }

+        }

+        else

+        {

+            viewId = externalContext.getRequestPathInfo();

+            if (viewId != null && traceEnabled)

+            {

+                log.trace("Calculated viewId '" + viewId

+                        + "' from request path info");

+            }

+        }

+

+        if (viewId == null)

+        {

+            viewId = (String) requestMap

+                    .get(JAVAX_SERVLET_INCLUDE_SERVLET_PATH);

+            if (viewId != null && traceEnabled)

+            {

+                log.trace("Calculated viewId '" + viewId

+                        + "' from request param '"

+                        + JAVAX_SERVLET_INCLUDE_SERVLET_PATH + "'");

+            }

+        }

+

+        if (viewId == null)

+        {

+            viewId = externalContext.getRequestServletPath();

+            if (viewId != null && traceEnabled)

+            {

+                log.trace("Calculated viewId '" + viewId

+                        + "' from request servlet path");

+            }

+        }

+

+        if (viewId == null)

+        {

+            throw new FacesException("Could not determine view id.");

+        }

+

+        return viewId;

+    }

+

+    public boolean isPostback(FacesContext facesContext)

+    {

+        return true;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/InvokeApplicationExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/InvokeApplicationExecutor.java
new file mode 100644
index 0000000..d0d7cfd
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/InvokeApplicationExecutor.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.myfaces.test.mock.lifecycle;

+

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2

+ *

+ * Invoke application phase (JSF Spec 2.2.5)

+ * 

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ */

+class InvokeApplicationExecutor implements PhaseExecutor

+{

+    public boolean execute(FacesContext facesContext)

+    {

+        facesContext.getViewRoot().processApplication(facesContext);

+        return false;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.INVOKE_APPLICATION;

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycle.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycle.java
new file mode 100644
index 0000000..de199c8
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycle.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.test.mock.lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseListener;
+import javax.faces.lifecycle.Lifecycle;
+
+/**
+ * <p>Mock implementation of <code>Lifecycle</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockLifecycle extends Lifecycle
+{
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>List of event listeners for this instance.</p>
+     */
+    private List phaseListenerList = new ArrayList();
+    private PhaseExecutor[] lifecycleExecutors;
+    private PhaseExecutor renderExecutor;
+
+    public MockLifecycle()
+    {
+        lifecycleExecutors = new PhaseExecutor[] { new RestoreViewExecutor(),
+                new ApplyRequestValuesExecutor(),
+                new ProcessValidationsExecutor(),
+                new UpdateModelValuesExecutor(),
+                new InvokeApplicationExecutor() };
+
+        renderExecutor = new RenderResponseExecutor();
+    }
+
+    // ------------------------------------------------------- Lifecycle Methods
+
+    /** {@inheritDoc} */
+    public void addPhaseListener(PhaseListener listener)
+    {
+
+        phaseListenerList.add(listener);
+
+    }
+
+    /** {@inheritDoc} */
+    public void execute(FacesContext context) throws FacesException
+    {
+
+        PhaseListenerManager phaseListenerMgr = new PhaseListenerManager(this,
+                context, getPhaseListeners());
+        for (int executorIndex = 0; executorIndex < lifecycleExecutors.length; executorIndex++)
+        {
+            if (executePhase(context, lifecycleExecutors[executorIndex],
+                    phaseListenerMgr))
+            {
+                return;
+            }
+        }
+    }
+
+    private boolean executePhase(FacesContext facesContext,
+            PhaseExecutor executor, PhaseListenerManager phaseListenerMgr)
+            throws FacesException
+    {
+        boolean skipFurtherProcessing = false;
+
+        try
+        {
+            phaseListenerMgr.informPhaseListenersBefore(executor.getPhase());
+
+            if (isResponseComplete(facesContext, executor.getPhase(), true))
+            {
+                // have to return right away
+                return true;
+            }
+            if (shouldRenderResponse(facesContext, executor.getPhase(), true))
+            {
+                skipFurtherProcessing = true;
+            }
+
+            if (executor.execute(facesContext))
+            {
+                return true;
+            }
+        }
+        finally
+        {
+            phaseListenerMgr.informPhaseListenersAfter(executor.getPhase());
+        }
+
+        if (isResponseComplete(facesContext, executor.getPhase(), false)
+                || shouldRenderResponse(facesContext, executor.getPhase(),
+                        false))
+        {
+            // since this phase is completed we don't need to return right away even if the response is completed
+            skipFurtherProcessing = true;
+        }
+
+        return skipFurtherProcessing;
+    }
+
+    /** {@inheritDoc} */
+    public PhaseListener[] getPhaseListeners()
+    {
+
+        return (PhaseListener[]) phaseListenerList
+                .toArray(new PhaseListener[phaseListenerList.size()]);
+
+    }
+
+    /** {@inheritDoc} */
+    public void removePhaseListener(PhaseListener listener)
+    {
+
+        phaseListenerList.remove(listener);
+
+    }
+
+    /** {@inheritDoc} */
+    public void render(FacesContext context) throws FacesException
+    {
+
+        // if the response is complete we should not be invoking the phase listeners
+        if (isResponseComplete(context, renderExecutor.getPhase(), true))
+        {
+            return;
+        }
+
+        PhaseListenerManager phaseListenerMgr = new PhaseListenerManager(this,
+                context, getPhaseListeners());
+
+        try
+        {
+            phaseListenerMgr.informPhaseListenersBefore(renderExecutor
+                    .getPhase());
+            // also possible that one of the listeners completed the response
+            if (isResponseComplete(context, renderExecutor.getPhase(), true))
+            {
+                return;
+            }
+
+            renderExecutor.execute(context);
+        }
+        finally
+        {
+            phaseListenerMgr.informPhaseListenersAfter(renderExecutor
+                    .getPhase());
+        }
+
+    }
+
+    private boolean isResponseComplete(FacesContext facesContext,
+            PhaseId phase, boolean before)
+    {
+        boolean flag = false;
+        if (facesContext.getResponseComplete())
+        {
+            flag = true;
+        }
+        return flag;
+    }
+
+    private boolean shouldRenderResponse(FacesContext facesContext,
+            PhaseId phase, boolean before)
+    {
+        boolean flag = false;
+        if (facesContext.getRenderResponse())
+        {
+            flag = true;
+        }
+        return flag;
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycleFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycleFactory.java
new file mode 100644
index 0000000..24ca920
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/MockLifecycleFactory.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.myfaces.test.mock.lifecycle;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.faces.lifecycle.Lifecycle;
+import javax.faces.lifecycle.LifecycleFactory;
+
+/**
+ * <p>Mock implementation of <code>LifecycleFactory</code>.</p>
+ *
+ * $Id$
+ * @since 1.0.0
+ */
+
+public class MockLifecycleFactory extends LifecycleFactory
+{
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * <p>Return a default instance.</p>
+     */
+    public MockLifecycleFactory()
+    {
+
+        lifecycles = new HashMap();
+        lifecycles.put(LifecycleFactory.DEFAULT_LIFECYCLE, new MockLifecycle());
+
+    }
+
+    // ----------------------------------------------------- Mock Object Methods
+
+    // ------------------------------------------------------ Instance Variables
+
+    /**
+     * <p>The set of Lifecycle instances registered with us.</p>
+     */
+    private Map lifecycles = null;
+
+    // ------------------------------------------------ LifecycleFactory Methods
+
+    /** {@inheritDoc} */
+    public void addLifecycle(String lifecycleId, Lifecycle lifecycle)
+    {
+
+        lifecycles.put(lifecycleId, lifecycle);
+
+    }
+
+    /** {@inheritDoc} */
+    public Lifecycle getLifecycle(String lifecycleId)
+    {
+
+        return (Lifecycle) lifecycles.get(lifecycleId);
+
+    }
+
+    /** {@inheritDoc} */
+    public Iterator getLifecycleIds()
+    {
+
+        return lifecycles.keySet().iterator();
+
+    }
+
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseExecutor.java
new file mode 100644
index 0000000..54b7402
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseExecutor.java
@@ -0,0 +1,49 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.mock.lifecycle;

+

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the PhaseExecutor for a lifecycle

+ *

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ *

+ */

+interface PhaseExecutor

+{

+

+    /**

+     * Executes a phase of the JavaServer(tm) Faces lifecycle, like UpdateModelValues.

+     * The <code>execute</code> method is called by the lifecylce implementation's private

+     * <code>executePhase</code>.

+     * @param facesContext The <code>FacesContext</code> for the current request we are processing 

+     * @return <code>true</code> if execution should be stopped

+     */

+    boolean execute(FacesContext facesContext);

+

+    /**

+     * Returns the <code>PhaseId</code> for which the implemented executor is invoked 

+     * @return

+     */

+    PhaseId getPhase();

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseListenerManager.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseListenerManager.java
new file mode 100644
index 0000000..ca3a2b0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/PhaseListenerManager.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.myfaces.test.mock.lifecycle;

+

+import java.util.HashMap;

+import java.util.Map;

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseEvent;

+import javax.faces.event.PhaseId;

+import javax.faces.event.PhaseListener;

+import javax.faces.lifecycle.Lifecycle;

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+

+/**

+ * This class encapsulates the logic used to call PhaseListeners.  It was 

+ * needed because of issue 9 of the JSF 1.2 spec.  See section 11.3 for more

+ * details.

+ *

+ * @author Stan Silvert

+ * @since 1.0.0

+ */

+class PhaseListenerManager

+{

+

+    private static final Log log = LogFactory

+            .getLog(PhaseListenerManager.class);

+

+    private Lifecycle lifecycle;

+    private FacesContext facesContext;

+    private PhaseListener[] phaseListeners;

+

+    // Tracks success in the beforePhase.  Listeners that throw an exception

+    // in beforePhase or were never called because a previous listener threw

+    // an exception should not have its afterPhase called

+    //private Map<PhaseId, boolean[]> listenerSuccessMap = new HashMap<PhaseId, boolean[]>();

+    private Map listenerSuccessMap = new HashMap();

+

+    /** Creates a new instance of PhaseListenerManager */

+    PhaseListenerManager(Lifecycle lifecycle, FacesContext facesContext,

+            PhaseListener[] phaseListeners)

+    {

+        this.lifecycle = lifecycle;

+        this.facesContext = facesContext;

+        this.phaseListeners = phaseListeners;

+    }

+

+    private boolean isListenerForThisPhase(PhaseListener phaseListener,

+            PhaseId phaseId)

+    {

+        int listenerPhaseId = phaseListener.getPhaseId().getOrdinal();

+        return (listenerPhaseId == PhaseId.ANY_PHASE.getOrdinal() || listenerPhaseId == phaseId

+                .getOrdinal());

+    }

+

+    void informPhaseListenersBefore(PhaseId phaseId)

+    {

+        boolean[] beforePhaseSuccess = new boolean[phaseListeners.length];

+        listenerSuccessMap.put(phaseId, beforePhaseSuccess);

+

+        for (int i = 0; i < phaseListeners.length; i++)

+        {

+            PhaseListener phaseListener = phaseListeners[i];

+            if (isListenerForThisPhase(phaseListener, phaseId))

+            {

+                try

+                {

+                    phaseListener.beforePhase(new PhaseEvent(facesContext,

+                            phaseId, lifecycle));

+                    beforePhaseSuccess[i] = true;

+                }

+                catch (Exception e)

+                {

+                    beforePhaseSuccess[i] = false; // redundant - for clarity

+                    log.error("Exception in PhaseListener "

+                            + phaseId.toString() + " beforePhase.", e);

+                    return;

+                }

+            }

+        }

+    }

+

+    void informPhaseListenersAfter(PhaseId phaseId)

+    {

+        //boolean[] beforePhaseSuccess = listenerSuccessMap.get(phaseId);

+        boolean[] beforePhaseSuccess = (boolean[]) listenerSuccessMap

+                .get(phaseId);

+

+        for (int i = phaseListeners.length - 1; i >= 0; i--)

+        {

+            PhaseListener phaseListener = phaseListeners[i];

+            if (isListenerForThisPhase(phaseListener, phaseId)

+                    && beforePhaseSuccess[i])

+            {

+                try

+                {

+                    phaseListener.afterPhase(new PhaseEvent(facesContext,

+                            phaseId, lifecycle));

+                }

+                catch (Exception e)

+                {

+                    log.error("Exception in PhaseListener "

+                            + phaseId.toString() + " afterPhase", e);

+                }

+            }

+        }

+

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ProcessValidationsExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ProcessValidationsExecutor.java
new file mode 100644
index 0000000..8f5ab67
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/ProcessValidationsExecutor.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.myfaces.test.mock.lifecycle;

+

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2

+ *

+ * Process validations phase (JSF Spec 2.2.3)

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ */

+class ProcessValidationsExecutor implements PhaseExecutor

+{

+    public boolean execute(FacesContext facesContext)

+    {

+        facesContext.getViewRoot().processValidators(facesContext);

+        return false;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.PROCESS_VALIDATIONS;

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RenderResponseExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RenderResponseExecutor.java
new file mode 100644
index 0000000..6d6ed33
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RenderResponseExecutor.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.myfaces.test.mock.lifecycle;

+

+import java.io.IOException;

+

+import javax.faces.FacesException;

+import javax.faces.application.Application;

+import javax.faces.application.ViewHandler;

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2

+ *

+ * render response phase (JSF Spec 2.2.6)

+ * 

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ */

+class RenderResponseExecutor implements PhaseExecutor

+{

+    public boolean execute(FacesContext facesContext)

+    {

+        Application application = facesContext.getApplication();

+        ViewHandler viewHandler = application.getViewHandler();

+

+        try

+        {

+            viewHandler.renderView(facesContext, facesContext.getViewRoot());

+        }

+        catch (IOException e)

+        {

+            throw new FacesException(e.getMessage(), e);

+        }

+        return false;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.RENDER_RESPONSE;

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewExecutor.java
new file mode 100644
index 0000000..4e92def
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewExecutor.java
@@ -0,0 +1,199 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.mock.lifecycle;

+

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+import org.apache.myfaces.test.util.Jsf11Utils;

+import org.apache.myfaces.test.util.Jsf12Utils;

+import org.apache.myfaces.test.util.JsfVersion;

+

+import javax.faces.FacesException;

+import javax.faces.application.Application;

+import javax.faces.application.ViewHandler;

+import javax.faces.component.UIViewRoot;

+import javax.faces.context.ExternalContext;

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the Restore View Phase (JSF Spec 2.2.1)

+ * 

+ * @author Nikolay Petrov

+ * @author Bruno Aranda (JSF 1.2)

+ * @version $Revision: 517403 $ $Date: 2007-03-12 22:17:00 +0100 (Mo, 12 Mrz 2007) $

+ * @since 1.0.0

+ */

+class RestoreViewExecutor implements PhaseExecutor

+{

+

+    private static final Log log = LogFactory.getLog(RestoreViewExecutor.class);

+    private RestoreViewSupport _restoreViewSupport;

+

+    public boolean execute(FacesContext facesContext)

+    {

+        if (facesContext == null)

+        {

+            throw new FacesException("FacesContext is null");

+        }

+

+        // init the View

+        Application application = facesContext.getApplication();

+        ViewHandler viewHandler = application.getViewHandler();

+        if (JsfVersion.supports12())

+        {

+          Jsf12Utils.initView(facesContext, viewHandler);

+        }

+        else

+        {

+          // nothing to do

+        }

+

+        UIViewRoot viewRoot = facesContext.getViewRoot();

+

+        RestoreViewSupport restoreViewSupport = getRestoreViewSupport();

+

+        if (viewRoot != null)

+        {

+            if (log.isTraceEnabled())

+            {

+                log.trace("View already exists in the FacesContext");

+            }

+

+            viewRoot.setLocale(facesContext.getExternalContext()

+                    .getRequestLocale());

+            restoreViewSupport.processComponentBinding(facesContext, viewRoot);

+            return false;

+        }

+

+        String viewId = restoreViewSupport.calculateViewId(facesContext);

+

+        // Determine if this request is a postback or initial request

+        if (restoreViewSupport.isPostback(facesContext))

+        {

+            if (log.isTraceEnabled())

+            {

+                log.trace("Request is a postback");

+            }

+

+            viewRoot = viewHandler.restoreView(facesContext, viewId);

+            if (viewRoot == null)

+            {

+              if (JsfVersion.supports12())

+              {

+                Jsf12Utils.throwViewExpiredException(viewId);

+              }

+              else

+              {

+                Jsf11Utils.throwViewExpiredException(viewId);

+              }

+            }

+            restoreViewSupport.processComponentBinding(facesContext, viewRoot);

+        }

+        else

+        {

+            if (log.isTraceEnabled())

+            {

+                log

+                        .trace("Request is not a postback. New UIViewRoot will be created");

+            }

+

+            viewRoot = viewHandler.createView(facesContext, viewId);

+            facesContext.renderResponse();

+        }

+

+        facesContext.setViewRoot(viewRoot);

+

+        return false;

+    }

+

+    protected RestoreViewSupport getRestoreViewSupport()

+    {

+        if (_restoreViewSupport == null)

+        {

+            _restoreViewSupport = new DefaultRestoreViewSupport();

+        }

+        return _restoreViewSupport;

+    }

+

+    /**

+     * @param restoreViewSupport

+     *            the restoreViewSupport to set

+     */

+    public void setRestoreViewSupport(RestoreViewSupport restoreViewSupport)

+    {

+        _restoreViewSupport = restoreViewSupport;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.RESTORE_VIEW;

+    }

+

+    /**

+     * TODO place that stuff into the default view handler implementation.

+     */

+    private static String deriveViewId(FacesContext facesContext)

+    {

+        ExternalContext externalContext = facesContext.getExternalContext();

+

+        //        if (PortletUtil.isPortletRequest(facesContext))

+        //        {

+        //            PortletRequest request = (PortletRequest) externalContext.getRequest();

+        //            return request.getParameter(MyFacesGenericPortlet.VIEW_ID);

+        //        }

+        //

+        String viewId = externalContext.getRequestPathInfo(); // getPathInfo

+        if (viewId == null)

+        {

+            // No extra path info found, so it is propably extension mapping

+            viewId = externalContext.getRequestServletPath(); // getServletPath

+            //            DebugUtils.assertError(viewId != null, log,

+            //                    "RequestServletPath is null, cannot determine viewId of current page.");

+            if (viewId == null)

+            {

+                return null;

+            }

+

+            // TODO: JSF Spec 2.2.1 - what do they mean by "if the default

+            // ViewHandler implementation is used..." ?

+            String defaultSuffix = externalContext

+                    .getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);

+            String suffix = defaultSuffix != null ? defaultSuffix

+                    : ViewHandler.DEFAULT_SUFFIX;

+            //            DebugUtils.assertError(suffix.charAt(0) == '.', log, "Default suffix must start with a dot!");

+

+            int dot = viewId.lastIndexOf('.');

+            if (dot == -1)

+            {

+                log

+                        .error("Assumed extension mapping, but there is no extension in "

+                                + viewId);

+                viewId = null;

+            }

+            else

+            {

+                viewId = viewId.substring(0, dot) + suffix;

+            }

+        }

+

+        return viewId;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewSupport.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewSupport.java
new file mode 100644
index 0000000..d750a33
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/RestoreViewSupport.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.myfaces.test.mock.lifecycle;

+

+import javax.faces.component.UIComponent;

+import javax.faces.context.FacesContext;

+

+/**

+ * Support class for restore view phase

+ * 

+ * @author Mathias Broekelmann (latest modification by $Author: mbr $)

+ * @version $Revision: 517403 $ $Date: 2007-03-12 22:17:00 +0100 (Mo, 12 Mrz 2007) $

+ * @since 1.0.0

+ */

+public interface RestoreViewSupport

+{

+    /**

+     * <p>

+     * Calculates the view id from the given faces context by the following algorithm

+     * </p>

+     * <ul>

+     * <li>lookup the viewid from the request attribute "javax.servlet.include.path_info"

+     * <li>if null lookup the value for viewid by {@link ExternalContext#getRequestPathInfo()}

+     * <li>if null lookup the value for viewid from the request attribute "javax.servlet.include.servlet_path"

+     * <li>if null lookup the value for viewid by {@link ExternalContext#getRequestServletPath()}

+     * <li>if null throw a {@link FacesException}

+     * </ul>

+     */

+    String calculateViewId(FacesContext facesContext);

+

+    /**

+     * Processes the component tree. For each component (including the given one) in the tree determine if a value

+     * expression for the attribute "binding" is defined. If the expression is not null set the component instance to

+     * the value of this expression

+     * 

+     * @param facesContext

+     * @param component

+     *            the root component

+     */

+    void processComponentBinding(FacesContext facesContext,

+            UIComponent component);

+

+    /**

+     * <p>

+     * Determine if the current request is a post back by the following algorithm.

+     * </p>

+     * <p>

+     * Find the render-kit-id for the current request by calling calculateRenderKitId() on the Application’s

+     * ViewHandler. Get that RenderKit’s ResponseStateManager and call its isPostback() method, passing the given

+     * FacesContext.

+     * </p>

+     * 

+     * @param facesContext

+     * @return

+     */

+    boolean isPostback(FacesContext facesContext);

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/UpdateModelValuesExecutor.java b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/UpdateModelValuesExecutor.java
new file mode 100644
index 0000000..f1e7832
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/lifecycle/UpdateModelValuesExecutor.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.myfaces.test.mock.lifecycle;

+

+import javax.faces.context.FacesContext;

+import javax.faces.event.PhaseId;

+

+/**

+ * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2

+ *

+ * Update model values phase (JSF Spec 2.2.4)

+ * 

+ * @author Nikolay Petrov

+ * @since 1.0.0

+ */

+class UpdateModelValuesExecutor implements PhaseExecutor

+{

+    public boolean execute(FacesContext facesContext)

+    {

+        facesContext.getViewRoot().processUpdates(facesContext);

+        return false;

+    }

+

+    public PhaseId getPhase()

+    {

+        return PhaseId.UPDATE_MODEL_VALUES;

+    }

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockClassLoaderResourceLoader.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockClassLoaderResourceLoader.java
new file mode 100644
index 0000000..bc38e07
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockClassLoaderResourceLoader.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.myfaces.test.mock.resource;

+

+import java.io.InputStream;

+import java.net.URL;

+

+/**

+ * A resource loader implementation which loads resources from the thread ClassLoader.

+ * 

+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)

+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $

+ * @since 1.0.0

+ */

+public class MockClassLoaderResourceLoader extends MockResourceLoader

+{

+    private ClassLoader _classLoader;

+

+    public MockClassLoaderResourceLoader(ClassLoader loader, String prefix)

+    {

+        super(prefix);

+        _classLoader = loader;

+    }

+

+    @Override

+    public String getLibraryVersion(String path)

+    {

+        return null;

+    }

+

+    @Override

+    public InputStream getResourceInputStream(MockResourceMeta resourceMeta)

+    {

+        if (getPrefix() != null && !"".equals(getPrefix()))

+        {

+            return getClassLoader().getResourceAsStream(

+                    getPrefix() + '/' + resourceMeta.getResourceIdentifier());

+        }

+        else

+        {

+            return getClassLoader().getResourceAsStream(

+                    resourceMeta.getResourceIdentifier());

+        }

+    }

+

+    @Override

+    public URL getResourceURL(MockResourceMeta resourceMeta)

+    {

+        if (getPrefix() != null && !"".equals(getPrefix()))

+        {

+            return getClassLoader().getResource(

+                    getPrefix() + '/' + resourceMeta.getResourceIdentifier());

+        }

+        else

+        {

+            return getClassLoader().getResource(

+                    resourceMeta.getResourceIdentifier());

+        }

+    }

+

+    @Override

+    public String getResourceVersion(String path)

+    {

+        return null;

+    }

+

+    @Override

+    public MockResourceMeta createResourceMeta(String prefix,

+            String libraryName, String libraryVersion, String resourceName,

+            String resourceVersion)

+    {

+        return new MockResourceMeta(prefix, libraryName, libraryVersion,

+                resourceName, resourceVersion);

+    }

+

+    /**

+     * Returns the ClassLoader to use when looking up resources under the top level package. By default, this is the

+     * context class loader.

+     * 

+     * @return the ClassLoader used to lookup resources

+     */

+    public ClassLoader getClassLoader()

+    {

+        return _classLoader;

+    }

+

+    public void setClassLoader(ClassLoader loader)

+    {

+        _classLoader = loader;

+    }

+

+    @Override

+    public boolean libraryExists(String libraryName)

+    {

+        if (getPrefix() != null && !"".equals(getPrefix()))

+        {

+            URL url = getClassLoader().getResource(

+                    getPrefix() + '/' + libraryName);

+            if (url != null)

+            {

+                return true;

+            }

+        }

+        else

+        {

+            URL url = getClassLoader().getResource(libraryName);

+            if (url != null)

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockExternalContextResourceLoader.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockExternalContextResourceLoader.java
new file mode 100644
index 0000000..388d617
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockExternalContextResourceLoader.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.myfaces.test.mock.resource;

+

+import java.io.InputStream;

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.util.Iterator;

+import java.util.Set;

+import java.util.regex.Pattern;

+

+import javax.faces.context.FacesContext;

+

+/**

+ * A resource loader implementation which loads resources from the webapp root. 

+ * It uses the methods on ExternalContext for handle resources.

+ * 

+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)

+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $

+ * @since 1.0.0

+ */

+public class MockExternalContextResourceLoader extends MockResourceLoader

+{

+    /**

+     * It checks version like this: /1/, /1_0/, /1_0_0/, /100_100/

+     * 

+     * Used on getLibraryVersion to filter resource directories

+     **/

+    protected static final Pattern VERSION_CHECKER = Pattern

+            .compile("/\\p{Digit}+(_\\p{Digit}*)*/");

+

+    /**

+     * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js

+     * 

+     * Used on getResourceVersion to filter resources

+     **/

+    protected static final Pattern RESOURCE_VERSION_CHECKER = Pattern

+            .compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");

+

+    public MockExternalContextResourceLoader(String prefix)

+    {

+        super(prefix);

+    }

+

+    protected Set<String> getResourcePaths(String path)

+    {

+        return FacesContext.getCurrentInstance().getExternalContext()

+                .getResourcePaths(getPrefix() + '/' + path);

+    }

+

+    @Override

+    public String getResourceVersion(String path)

+    {

+        String resourceVersion = null;

+        Set<String> resourcePaths = this.getResourcePaths(path);

+        if (getPrefix() != null)

+        {

+            path = getPrefix() + '/' + path;

+        }

+

+        if (null != resourcePaths && !resourcePaths.isEmpty())

+        {

+            // resourceVersion = // execute the comment

+            // Look in the resourcePaths for versioned resources.

+            // If one or more versioned resources are found, take

+            // the one with the "highest" version number as the value

+            // of resourceVersion. If no versioned libraries

+            // are found, let resourceVersion remain null.

+            for (String resourcePath : resourcePaths)

+            {

+                String version = resourcePath.substring(path.length());

+

+                if (RESOURCE_VERSION_CHECKER.matcher(version).matches())

+                {

+                    version = version.substring(1, version.lastIndexOf('.'));

+                    if (resourceVersion == null)

+                    {

+                        resourceVersion = version;

+                    }

+                    else if (getVersionComparator().compare(resourceVersion,

+                            version) < 0)

+                    {

+                        resourceVersion = version;

+                    }

+                }

+            }

+            //Since it is a directory and no version was found, set as invalid

+            if (resourceVersion == null)

+            {

+                resourceVersion = VERSION_INVALID;

+            }

+        }

+        return resourceVersion;

+    }

+

+    @Override

+    public String getLibraryVersion(String path)

+    {

+        String libraryVersion = null;

+        Set<String> libraryPaths = this.getResourcePaths(path);

+        path = getPrefix() + '/' + path;

+        if (null != libraryPaths && !libraryPaths.isEmpty())

+        {

+            // Look in the libraryPaths for versioned libraries.

+            // If one or more versioned libraries are found, take

+            // the one with the "highest" version number as the value

+            // of libraryVersion. If no versioned libraries

+            // are found, let libraryVersion remain null.

+

+            for (Iterator<String> it = libraryPaths.iterator(); it.hasNext();)

+            {

+                String libraryPath = it.next();

+                String version = libraryPath.substring(path.length());

+

+                if (VERSION_CHECKER.matcher(version).matches())

+                {

+                    version = version.substring(1, version.length() - 1);

+                    if (libraryVersion == null)

+                    {

+                        libraryVersion = version;

+                    }

+                    else if (getVersionComparator().compare(libraryVersion,

+                            version) < 0)

+                    {

+                        libraryVersion = version;

+                    }

+                }

+            }

+        }

+        return libraryVersion;

+    }

+

+    @Override

+    public URL getResourceURL(MockResourceMeta resourceMeta)

+    {

+        try

+        {

+            return FacesContext.getCurrentInstance().getExternalContext()

+                    .getResource(

+                            getPrefix() + '/'

+                                    + resourceMeta.getResourceIdentifier());

+        }

+        catch (MalformedURLException e)

+        {

+            return null;

+        }

+    }

+

+    @Override

+    public InputStream getResourceInputStream(MockResourceMeta resourceMeta)

+    {

+        return FacesContext.getCurrentInstance().getExternalContext()

+                .getResourceAsStream(

+                        getPrefix() + '/'

+                                + resourceMeta.getResourceIdentifier());

+    }

+

+    @Override

+    public MockResourceMeta createResourceMeta(String prefix,

+            String libraryName, String libraryVersion, String resourceName,

+            String resourceVersion)

+    {

+        return new MockResourceMeta(prefix, libraryName, libraryVersion,

+                resourceName, resourceVersion);

+    }

+

+    @Override

+    public boolean libraryExists(String libraryName)

+    {

+        if (getPrefix() != null && !"".equals(getPrefix()))

+        {

+            try

+            {

+                URL url = FacesContext.getCurrentInstance()

+                        .getExternalContext().getResource(

+                                getPrefix() + '/' + libraryName);

+                if (url != null)

+                {

+                    return true;

+                }

+            }

+            catch (MalformedURLException e)

+            {

+                return false;

+            }

+        }

+        else

+        {

+            try

+            {

+

+                URL url = FacesContext.getCurrentInstance()

+                        .getExternalContext().getResource(libraryName);

+

+                if (url != null)

+                {

+                    return true;

+                }

+            }

+            catch (MalformedURLException e)

+            {

+                return false;

+            }

+        }

+        return false;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.java
new file mode 100644
index 0000000..436e3ce
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.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.myfaces.test.mock.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>Resource</code>.</p>
+ * <p/>
+ * @author Leonardo Uribe (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ * @since 1.0.0
+ */
+public class MockResource extends Resource
+{
+    private MockResourceMeta _resourceMeta;
+    private MockResourceLoader _resourceLoader;
+    private MockResourceHandlerSupport _resourceHandlerSupport;
+
+    public MockResource(MockResourceMeta resourceMeta,
+            MockResourceLoader resourceLoader,
+            MockResourceHandlerSupport support, String contentType)
+    {
+        _resourceMeta = resourceMeta;
+        _resourceLoader = resourceLoader;
+        _resourceHandlerSupport = support;
+        setLibraryName(resourceMeta.getLibraryName());
+        setResourceName(resourceMeta.getResourceName());
+        setContentType(contentType);
+    }
+
+    public MockResourceLoader getResourceLoader()
+    {
+        return _resourceLoader;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return getResourceLoader().getResourceInputStream(_resourceMeta);
+    }
+
+    @Override
+    public String getRequestPath()
+    {
+        String path;
+        if (_resourceHandlerSupport.isExtensionMapping())
+        {
+            path = ResourceHandler.RESOURCE_IDENTIFIER + '/'
+                    + getResourceName() + _resourceHandlerSupport.getMapping();
+        }
+        else
+        {
+            String mapping = _resourceHandlerSupport.getMapping();
+            path = ResourceHandler.RESOURCE_IDENTIFIER + '/'
+                    + getResourceName();
+            path = (mapping == null) ? path : mapping + path;
+        }
+
+        return path;
+    }
+
+    @Override
+    public Map<String, String> getResponseHeaders()
+    {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public URL getURL()
+    {
+        return getResourceLoader().getResourceURL(_resourceMeta);
+    }
+
+    @Override
+    public boolean userAgentNeedsUpdate(FacesContext context)
+    {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.java
new file mode 100644
index 0000000..a12cf48
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.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.myfaces.test.mock.resource;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.faces.FacesException;
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>ResourceHandler</code>.</p>
+ * <p>This ResourceHandler implementation try to follow the default algorithm
+ * defined by the spec, so it try to load resources using the current 
+ * ExternalContext and the specified ClassLoader, in the same locations
+ * it is expected ("resources" and "META-INF/resources").</p>
+ * 
+ * @author Leonardo Uribe (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ * @since 1.0.0
+ */
+public class MockResourceHandler extends ResourceHandler
+{
+
+    private boolean _resourceRequest;
+
+    private MockResourceHandlerSupport resourceHandlerSupport;
+
+    private ClassLoader _classLoader;
+
+    public MockResourceHandler()
+    {
+        _classLoader = getContextClassLoader();
+        resourceHandlerSupport = new MockResourceHandlerSupport(true, ".jsf",
+                _classLoader);
+    }
+
+    public MockResourceHandler(ClassLoader classLoader)
+    {
+        if (classLoader == null)
+        {
+            _classLoader = getContextClassLoader();
+        }
+        else
+        {
+            _classLoader = classLoader;
+        }
+
+        resourceHandlerSupport = new MockResourceHandlerSupport(true, ".jsf",
+                _classLoader);
+    }
+
+    public MockResourceHandler(boolean extensionMapping, String mapping,
+            ClassLoader classLoader)
+    {
+        if (classLoader == null)
+        {
+            _classLoader = getContextClassLoader();
+        }
+        else
+        {
+            _classLoader = classLoader;
+        }
+
+        resourceHandlerSupport = new MockResourceHandlerSupport(
+                extensionMapping, mapping, _classLoader);
+    }
+
+    @Override
+    public Resource createResource(String resourceName)
+    {
+        return createResource(resourceName, null);
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName)
+    {
+        return createResource(resourceName, libraryName, null);
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName,
+            String contentType)
+    {
+        Resource resource = null;
+
+        if (contentType == null)
+        {
+            //Resolve contentType using ExternalContext.getMimeType
+            contentType = FacesContext.getCurrentInstance()
+                    .getExternalContext().getMimeType(resourceName);
+        }
+
+        for (MockResourceLoader loader : getResourceHandlerSupport()
+                .getResourceLoaders())
+        {
+            MockResourceMeta resourceMeta = deriveResourceMeta(loader,
+                    resourceName, libraryName);
+
+            if (resourceMeta != null)
+            {
+                resource = new MockResource(resourceMeta, loader,
+                        getResourceHandlerSupport(), contentType);
+                break;
+            }
+        }
+        return resource;
+    }
+
+    /**
+     * This method try to create a ResourceMeta for a specific resource
+     * loader. If no library, or resource is found, just return null,
+     * so the algorithm in createResource can continue checking with the 
+     * next registered ResourceLoader. 
+     */
+    protected MockResourceMeta deriveResourceMeta(
+            MockResourceLoader resourceLoader, String resourceName,
+            String libraryName)
+    {
+        String localePrefix = getLocalePrefixForLocateResource();
+        String resourceVersion = null;
+        String libraryVersion = null;
+        MockResourceMeta resourceId = null;
+
+        //1. Try to locate resource in a localized path
+        if (localePrefix != null)
+        {
+            if (null != libraryName)
+            {
+                String pathToLib = localePrefix + '/' + libraryName;
+                libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
+
+                if (null != libraryVersion)
+                {
+                    String pathToResource = localePrefix + '/' + libraryName
+                            + '/' + libraryVersion + '/' + resourceName;
+                    resourceVersion = resourceLoader
+                            .getResourceVersion(pathToResource);
+                }
+                else
+                {
+                    String pathToResource = localePrefix + '/' + libraryName
+                            + '/' + resourceName;
+                    resourceVersion = resourceLoader
+                            .getResourceVersion(pathToResource);
+                }
+
+                if (!(resourceVersion != null && MockResourceLoader.VERSION_INVALID
+                        .equals(resourceVersion)))
+                {
+                    resourceId = resourceLoader.createResourceMeta(
+                            localePrefix, libraryName, libraryVersion,
+                            resourceName, resourceVersion);
+                }
+            }
+            else
+            {
+                resourceVersion = resourceLoader
+                        .getResourceVersion(localePrefix + '/' + resourceName);
+                if (!(resourceVersion != null && MockResourceLoader.VERSION_INVALID
+                        .equals(resourceVersion)))
+                {
+                    resourceId = resourceLoader.createResourceMeta(
+                            localePrefix, null, null, resourceName,
+                            resourceVersion);
+                }
+            }
+
+            if (resourceId != null)
+            {
+                URL url = resourceLoader.getResourceURL(resourceId);
+                if (url == null)
+                {
+                    resourceId = null;
+                }
+            }
+        }
+
+        //2. Try to localize resource in a non localized path
+        if (resourceId == null)
+        {
+            if (null != libraryName)
+            {
+                libraryVersion = resourceLoader.getLibraryVersion(libraryName);
+
+                if (null != libraryVersion)
+                {
+                    String pathToResource = (libraryName + '/' + libraryVersion
+                            + '/' + resourceName);
+                    resourceVersion = resourceLoader
+                            .getResourceVersion(pathToResource);
+                }
+                else
+                {
+                    String pathToResource = (libraryName + '/' + resourceName);
+                    resourceVersion = resourceLoader
+                            .getResourceVersion(pathToResource);
+                }
+
+                if (!(resourceVersion != null && MockResourceLoader.VERSION_INVALID
+                        .equals(resourceVersion)))
+                {
+                    resourceId = resourceLoader.createResourceMeta(null,
+                            libraryName, libraryVersion, resourceName,
+                            resourceVersion);
+                }
+            }
+            else
+            {
+                resourceVersion = resourceLoader
+                        .getResourceVersion(resourceName);
+
+                if (!(resourceVersion != null && MockResourceLoader.VERSION_INVALID
+                        .equals(resourceVersion)))
+                {
+                    resourceId = resourceLoader.createResourceMeta(null, null,
+                            null, resourceName, resourceVersion);
+                }
+            }
+
+            if (resourceId != null)
+            {
+                URL url = resourceLoader.getResourceURL(resourceId);
+                if (url == null)
+                {
+                    resourceId = null;
+                }
+            }
+        }
+
+        return resourceId;
+    }
+
+    @Override
+    public String getRendererTypeForResourceName(String resourceName)
+    {
+        if (resourceName.endsWith(".js"))
+        {
+            return "javax.faces.resource.Script";
+        }
+        else if (resourceName.endsWith(".css"))
+        {
+            return "javax.faces.resource.Stylesheet";
+        }
+        return null;
+    }
+
+    @Override
+    public void handleResourceRequest(FacesContext context) throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isResourceRequest(FacesContext facesContext)
+    {
+        return _resourceRequest;
+    }
+
+    @Override
+    public boolean libraryExists(String libraryName)
+    {
+        String localePrefix = getLocalePrefixForLocateResource();
+
+        String pathToLib;
+
+        if (localePrefix != null)
+        {
+            //Check with locale
+            pathToLib = localePrefix + '/' + libraryName;
+        }
+        else
+        {
+            pathToLib = libraryName;
+        }
+
+        try
+        {
+            URL url = FacesContext.getCurrentInstance().getExternalContext()
+                    .getResource("/" + pathToLib);
+            return (url != null);
+        }
+        catch (MalformedURLException e)
+        {
+            return false;
+        }
+    }
+
+    protected String getLocalePrefixForLocateResource()
+    {
+        String localePrefix = null;
+        FacesContext context = FacesContext.getCurrentInstance();
+
+        String bundleName = context.getApplication().getMessageBundle();
+
+        if (null != bundleName)
+        {
+            Locale locale = context.getApplication().getViewHandler()
+                    .calculateLocale(context);
+
+            ResourceBundle bundle = ResourceBundle.getBundle(bundleName,
+                    locale, getContextClassLoader());
+
+            if (bundle != null)
+            {
+                try
+                {
+                    localePrefix = bundle
+                            .getString(ResourceHandler.LOCALE_PREFIX);
+                }
+                catch (MissingResourceException e)
+                {
+                    // Ignore it and return null
+                }
+            }
+        }
+        return localePrefix;
+    }
+
+    /**
+     * Gets the ClassLoader associated with the current thread.  Includes a check for priviledges
+     * against java2 security to ensure no security related exceptions are encountered.
+     *
+     * @return ClassLoader
+     */
+    static ClassLoader getContextClassLoader()
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                ClassLoader cl = AccessController
+                        .doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
+                        {
+                            public ClassLoader run()
+                                    throws PrivilegedActionException
+                            {
+                                return Thread.currentThread()
+                                        .getContextClassLoader();
+                            }
+                        });
+                return cl;
+            }
+            catch (PrivilegedActionException pae)
+            {
+                throw new FacesException(pae);
+            }
+        }
+        else
+        {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+
+    public MockResourceHandlerSupport getResourceHandlerSupport()
+    {
+        return resourceHandlerSupport;
+    }
+
+    public void setResourceHandlerSupport(
+            MockResourceHandlerSupport resourceHandlerSupport)
+    {
+        this.resourceHandlerSupport = resourceHandlerSupport;
+    }
+
+    public void setResourceRequest(boolean resourceRequest)
+    {
+        this._resourceRequest = resourceRequest;
+    }
+
+    public boolean isResourceRequest()
+    {
+        return _resourceRequest;
+    }
+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandlerSupport.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandlerSupport.java
new file mode 100644
index 0000000..5f423e5
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandlerSupport.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.myfaces.test.mock.resource;

+

+/**

+ * Store additional info used by MockResource and MockResourceHandler

+ * 

+ * @author Leonardo Uribe (latest modification by $Author: jakobk $)

+ * @version $Revision: 960906 $ $Date: 2010-07-06 09:45:40 -0500 (Mar, 06 Jul 2010) $

+ * @since 1.0.0

+ */

+public class MockResourceHandlerSupport

+{

+

+    private boolean _extensionMapping;

+

+    private String _mapping;

+

+    private MockResourceLoader[] _resourceLoaders;

+

+    public MockResourceHandlerSupport()

+    {

+        _extensionMapping = true;

+        _mapping = ".jsf";

+        _resourceLoaders = new MockResourceLoader[] {

+                new MockExternalContextResourceLoader("/resources"),

+                new MockClassLoaderResourceLoader(MockResourceHandler

+                        .getContextClassLoader(), "META-INF/resources") };

+    }

+

+    public MockResourceHandlerSupport(boolean extensionMapping, String mapping)

+    {

+        super();

+        _extensionMapping = extensionMapping;

+        _mapping = mapping;

+        _resourceLoaders = new MockResourceLoader[] {

+                new MockExternalContextResourceLoader("/resources"),

+                new MockClassLoaderResourceLoader(MockResourceHandler

+                        .getContextClassLoader(), "META-INF/resources") };

+    }

+

+    public MockResourceHandlerSupport(boolean extensionMapping, String mapping,

+            ClassLoader classLoader)

+    {

+        _extensionMapping = extensionMapping;

+        _mapping = mapping;

+        _resourceLoaders = new MockResourceLoader[] {

+                new MockExternalContextResourceLoader("/resources"),

+                new MockClassLoaderResourceLoader(classLoader,

+                        "META-INF/resources") };

+    }

+

+    public MockResourceHandlerSupport(boolean extensionMapping, String mapping,

+            MockResourceLoader[] resourceLoaders)

+    {

+        super();

+        _extensionMapping = extensionMapping;

+        _mapping = mapping;

+        _resourceLoaders = resourceLoaders;

+    }

+

+    /**

+     * Check if the mapping used is done using extensions (.xhtml, .jsf)

+     * or if it is not (/faces/*)

+     * @return

+     */

+    public boolean isExtensionMapping()

+    {

+        return _extensionMapping;

+    }

+

+    public void setExtensionMapping(boolean extensionMapping)

+    {

+        _extensionMapping = extensionMapping;

+    }

+

+    /**

+     * Get the mapping used as prefix(/faces) or sufix(.jsf)

+     * 

+     * @return

+     */

+    public String getMapping()

+    {

+        return _mapping;

+    }

+

+    public void setMapping(String prefix)

+    {

+        _mapping = prefix;

+    }

+

+    /**

+     * Return an array of resource loaders used to find resources

+     * using the standard. The order of ResourceLoaders define

+     * its precedence. 

+     * 

+     * @return

+     */

+    public MockResourceLoader[] getResourceLoaders()

+    {

+        return _resourceLoaders;

+    }

+

+    public void setResourceLoaders(MockResourceLoader[] resourceLoaders)

+    {

+        _resourceLoaders = resourceLoaders;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceLoader.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceLoader.java
new file mode 100644
index 0000000..33ba348
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceLoader.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.myfaces.test.mock.resource;

+

+import java.io.InputStream;

+import java.net.URL;

+import java.util.Comparator;

+

+/**

+ * Base class for resource loaders.  Resource loaders can lookup resources 

+ * as URLs from arbitrary locations, including JAR files.

+ * 

+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)

+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $

+ * @since 1.0.0

+ */

+public abstract class MockResourceLoader

+{

+

+    public static final String VERSION_INVALID = "INVALID";

+

+    private String _prefix;

+

+    public MockResourceLoader(String prefix)

+    {

+        _prefix = prefix;

+    }

+

+    public abstract String getResourceVersion(String path);

+

+    /**

+     * Return the max available version found (if exists) or

+     * return null if no version available. 

+     */

+    public abstract String getLibraryVersion(String path);

+

+    /**

+     * Return the max available version found (if exists) or

+     * return null if no version available. 

+     */

+    public abstract URL getResourceURL(MockResourceMeta resourceMeta);

+

+    public abstract InputStream getResourceInputStream(

+            MockResourceMeta resourceMeta);

+

+    public abstract MockResourceMeta createResourceMeta(String prefix,

+            String libraryName, String libraryVersion, String resourceName,

+            String resourceVersion);

+

+    public abstract boolean libraryExists(String libraryName);

+

+    private Comparator<String> _versionComparator = null;

+

+    protected Comparator<String> getVersionComparator()

+    {

+        if (_versionComparator == null)

+        {

+            _versionComparator = new VersionComparator();

+        }

+        return _versionComparator;

+    }

+

+    protected void setVersionComparator(Comparator<String> versionComparator)

+    {

+        _versionComparator = versionComparator;

+    }

+

+    public class VersionComparator implements Comparator<String>

+    {

+

+        public int compare(String s1, String s2)

+        {

+            int n1 = 0;

+            int n2 = 0;

+            String o1 = s1;

+            String o2 = s2;

+

+            boolean p1 = true;

+            boolean p2 = true;

+

+            while (n1 == n2 && (p1 || p2))

+            {

+                int i1 = o1.indexOf('_');

+                int i2 = o2.indexOf('_');

+                if (i1 < 0)

+                {

+                    if (o1.length() > 0)

+                    {

+                        p1 = false;

+                        n1 = Integer.valueOf(o1);

+                        o1 = "";

+                    }

+                    else

+                    {

+                        p1 = false;

+                        n1 = 0;

+                    }

+                }

+                else

+                {

+                    n1 = Integer.valueOf(o1.substring(0, i1));

+                    o1 = o1.substring(i1 + 1);

+                }

+                if (i2 < 0)

+                {

+                    if (o2.length() > 0)

+                    {

+                        p2 = false;

+                        n2 = Integer.valueOf(o2);

+                        o2 = "";

+                    }

+                    else

+                    {

+                        p2 = false;

+                        n2 = 0;

+                    }

+                }

+                else

+                {

+                    n2 = Integer.valueOf(o2.substring(0, i2));

+                    o2 = o2.substring(i2 + 1);

+                }

+            }

+

+            if (n1 == n2)

+            {

+                return s1.length() - s2.length();

+            }

+            return n1 - n2;

+        }

+    }

+

+    public String getPrefix()

+    {

+        return _prefix;

+    }

+

+    public void setPrefix(String prefix)

+    {

+        _prefix = prefix;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceMeta.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceMeta.java
new file mode 100644
index 0000000..dd8fc1d
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceMeta.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.myfaces.test.mock.resource;

+

+/**

+ * Contains the metadata information to reference a resource 

+ * 

+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)

+ * @version $Revision: 946779 $ $Date: 2010-05-20 15:31:42 -0500 (Jue, 20 May 2010) $

+ * @since 1.0.0

+ */

+public class MockResourceMeta

+{

+

+    private final String _prefix;

+    private final String _libraryName;

+    private final String _libraryVersion;

+    private final String _resourceName;

+    private final String _resourceVersion;

+

+    public MockResourceMeta(String prefix, String libraryName,

+            String libraryVersion, String resourceName, String resourceVersion)

+    {

+        _prefix = prefix;

+        _libraryName = libraryName;

+        _libraryVersion = libraryVersion;

+        _resourceName = resourceName;

+        _resourceVersion = resourceVersion;

+    }

+

+    public String getLibraryName()

+    {

+        return _libraryName;

+    }

+

+    public String getResourceName()

+    {

+        return _resourceName;

+    }

+

+    public String getLocalePrefix()

+    {

+        return _prefix;

+    }

+

+    public String getLibraryVersion()

+    {

+        return _libraryVersion;

+    }

+

+    public String getResourceVersion()

+    {

+        return _resourceVersion;

+    }

+

+    public String getResourceIdentifier()

+    {

+        StringBuilder builder = new StringBuilder();

+        boolean firstSlashAdded = false;

+        if (_prefix != null && _prefix.length() > 0)

+        {

+            builder.append(_prefix);

+            firstSlashAdded = true;

+        }

+        if (_libraryName != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_libraryName);

+            firstSlashAdded = true;

+        }

+        if (_libraryVersion != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_libraryVersion);

+            firstSlashAdded = true;

+        }

+        if (_resourceName != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_resourceName);

+            firstSlashAdded = true;

+        }

+        if (_resourceVersion != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_resourceVersion);

+            builder.append(_resourceName.substring(_resourceName

+                    .lastIndexOf('.')));

+            firstSlashAdded = true;

+        }

+

+        return builder.toString();

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResource.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResource.java
new file mode 100644
index 0000000..8629f5d
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResource.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.myfaces.test.mock.resource;

+

+import org.apache.myfaces.test.mock.MockServletContext;

+

+import javax.faces.application.Resource;

+import javax.faces.context.FacesContext;

+import java.io.File;

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.util.Map;

+

+/**

+ * <p>Mock simple implementation of <code>Resource</code>.</p>

+ * 

+ * <p>

+ * It is used by MockSimpleResourceHandler to wrap resource instances.

+ * </p>

+ * 

+ * @author Jakob Korherr (latest modification by $Author: lu4242 $)

+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $

+ * @since 1.0.0

+ */

+public class MockSimpleResource extends Resource

+{

+

+    private String _prefix;

+    private String _libraryName;

+    private String _libraryVersion;

+    private String _resourceName;

+    private String _resourceVersion;

+    private File _documentRoot;

+

+    /**

+     * Creates new resource object

+     *

+     * @param prefix          locale prefix if any

+     * @param libraryName     resource library name

+     * @param libraryVersion  resource library version if any

+     * @param resourceName    resource file name

+     * @param resourceVersion resource version if any

+     * @param documentRoot    parent folder of resource directories. Must not be <code>null</code>

+     */

+    public MockSimpleResource(String prefix, String libraryName,

+            String libraryVersion, String resourceName, String resourceVersion,

+            File documentRoot)

+    {

+        _prefix = prefix;

+        _libraryName = libraryName;

+        _libraryVersion = libraryVersion;

+        _resourceName = resourceName;

+        _resourceVersion = resourceVersion;

+        _documentRoot = documentRoot;

+

+        if (_documentRoot == null)

+        {

+            throw new IllegalArgumentException("documentRoot must not be null");

+        }

+    }

+

+    @Override

+    public String getResourceName()

+    {

+        return _resourceName;

+    }

+

+    @Override

+    public void setResourceName(String resourceName)

+    {

+        _resourceName = resourceName;

+    }

+

+    @Override

+    public String getLibraryName()

+    {

+        return _libraryName;

+    }

+

+    @Override

+    public void setLibraryName(String libraryName)

+    {

+        _libraryName = libraryName;

+    }

+

+    @Override

+    public InputStream getInputStream() throws IOException

+    {

+        MockServletContext servletContext = (MockServletContext) FacesContext

+                .getCurrentInstance().getExternalContext().getContext();

+        servletContext.setDocumentRoot(_documentRoot);

+        return servletContext.getResourceAsStream(buildResourcePath());

+    }

+

+    @Override

+    public String getRequestPath()

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public Map<String, String> getResponseHeaders()

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public URL getURL()

+    {

+        MockServletContext servletContext = (MockServletContext) FacesContext

+                .getCurrentInstance().getExternalContext().getContext();

+        servletContext.setDocumentRoot(_documentRoot);

+

+        try

+        {

+            return servletContext.getResource(buildResourcePath());

+        }

+        catch (MalformedURLException e)

+        {

+            return null;

+        }

+    }

+

+    @Override

+    public boolean userAgentNeedsUpdate(FacesContext context)

+    {

+        return true;

+    }

+

+    private String buildResourcePath()

+    {

+        StringBuilder builder = new StringBuilder();

+        builder.append('/');

+        boolean firstSlashAdded = false;

+        if (_prefix != null && _prefix.length() > 0)

+        {

+            builder.append(_prefix);

+            firstSlashAdded = true;

+        }

+        if (_libraryName != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_libraryName);

+            firstSlashAdded = true;

+        }

+        if (_libraryVersion != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_libraryVersion);

+            firstSlashAdded = true;

+        }

+        if (_resourceName != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_resourceName);

+            firstSlashAdded = true;

+        }

+        if (_resourceVersion != null)

+        {

+            if (firstSlashAdded)

+            {

+                builder.append('/');

+            }

+            builder.append(_resourceVersion);

+            builder.append(_resourceName.substring(_resourceName

+                    .lastIndexOf('.')));

+        }

+

+        return builder.toString();

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResourceHandler.java b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResourceHandler.java
new file mode 100644
index 0000000..b189e3a
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/resource/MockSimpleResourceHandler.java
@@ -0,0 +1,410 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.myfaces.test.mock.resource;

+

+import org.apache.myfaces.test.mock.MockServletContext;

+

+import javax.faces.FacesException;

+import javax.faces.application.Resource;

+import javax.faces.application.ResourceHandler;

+import javax.faces.context.ExternalContext;

+import javax.faces.context.FacesContext;

+import java.io.File;

+import java.io.IOException;

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.security.AccessController;

+import java.security.PrivilegedActionException;

+import java.security.PrivilegedExceptionAction;

+import java.util.Locale;

+import java.util.MissingResourceException;

+import java.util.ResourceBundle;

+import java.util.Set;

+import java.util.regex.Pattern;

+

+/**

+ * <p>Mock implementation of <code>ResourceHandler</code>.</p>

+ * <p>This Mock could be used on situations where all resources

+ * are on a specific path.</p>

+ * 

+ * @author Jakob Korherr (latest modification by $Author: lu4242 $)

+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $

+ * @since 1.0.0

+ */

+public class MockSimpleResourceHandler extends ResourceHandler

+{

+

+    private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.IS_RESOURCE_REQUEST";

+

+    /**

+     * It checks version like this: /1/, /1_0/, /1_0_0/, /100_100/

+     * <p/>

+     * Used on getLibraryVersion to filter resource directories

+     */

+    protected static final Pattern VERSION_CHECKER = Pattern

+            .compile("/\\p{Digit}+(_\\p{Digit}*)*/");

+

+    /**

+     * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js

+     * <p/>

+     * Used on getResourceVersion to filter resources

+     */

+    protected static final Pattern RESOURCE_VERSION_CHECKER = Pattern

+            .compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");

+

+    private File _documentRoot;

+

+    /**

+     * @param documentRoot parent folder of resource directories. Must not be <code>null</code>

+     */

+    public MockSimpleResourceHandler(File documentRoot)

+    {

+        if (documentRoot == null)

+        {

+            throw new NullPointerException("documentRoot must not be null");

+        }

+

+        _documentRoot = documentRoot;

+

+        ((MockServletContext) FacesContext.getCurrentInstance()

+                .getExternalContext().getContext())

+                .setDocumentRoot(_documentRoot);

+    }

+

+    @Override

+    public Resource createResource(String resourceName)

+    {

+        return createResource(resourceName, null);

+    }

+

+    @Override

+    public Resource createResource(String resourceName, String libraryName)

+    {

+        return createResource(resourceName, libraryName, null);

+    }

+

+    @Override

+    public Resource createResource(String resourceName, String libraryName,

+            String contentType)

+    {

+        String prefix = getLocalePrefixForLocateResource();

+        String libraryVersion = getLibraryVersion(prefix + "/" + libraryName);

+

+        String pathToResource;

+        if (null != libraryVersion)

+        {

+            pathToResource = prefix + '/' + libraryName + '/' + libraryVersion

+                    + '/' + resourceName;

+        }

+        else

+        {

+            pathToResource = prefix + '/' + libraryName + '/' + resourceName;

+        }

+

+        return new MockSimpleResource(prefix, libraryName, libraryVersion,

+                resourceName, getResourceVersion(pathToResource), _documentRoot);

+    }

+

+    @Override

+    public String getRendererTypeForResourceName(String resourceName)

+    {

+        if (resourceName.endsWith(".js"))

+        {

+            return "javax.faces.resource.Script";

+        }

+        else if (resourceName.endsWith(".css"))

+        {

+            return "javax.faces.resource.Stylesheet";

+        }

+        return null;

+    }

+

+    @Override

+    public void handleResourceRequest(FacesContext context) throws IOException

+    {

+        throw new UnsupportedOperationException();

+    }

+

+    @Override

+    public boolean isResourceRequest(FacesContext facesContext)

+    {

+        // Since this method could be called many times we save it

+        //on request map so the first time is calculated it remains

+        //alive until the end of the request

+        Boolean value = (Boolean) facesContext.getExternalContext()

+                .getRequestMap().get(IS_RESOURCE_REQUEST);

+

+        if (value != null && value.booleanValue())

+        {

+            //return the saved value

+            return value.booleanValue();

+        }

+        else

+        {

+            // assuming that we don't have servlet mapping

+            String resourceBasePath = facesContext.getExternalContext()

+                    .getRequestPathInfo();

+

+            if (resourceBasePath != null

+                    && resourceBasePath

+                            .startsWith(ResourceHandler.RESOURCE_IDENTIFIER))

+            {

+                facesContext.getExternalContext().getRequestMap().put(

+                        IS_RESOURCE_REQUEST, Boolean.TRUE);

+                return true;

+            }

+            else

+            {

+                facesContext.getExternalContext().getRequestMap().put(

+                        IS_RESOURCE_REQUEST, Boolean.FALSE);

+                return false;

+            }

+        }

+    }

+

+    @Override

+    public boolean libraryExists(String libraryName)

+    {

+        String localePrefix = getLocalePrefixForLocateResource();

+

+        String pathToLib;

+

+        if (localePrefix != null)

+        {

+            //Check with locale

+            pathToLib = localePrefix + '/' + libraryName;

+        }

+        else

+        {

+            pathToLib = libraryName;

+        }

+

+        try

+        {

+            URL url = FacesContext.getCurrentInstance().getExternalContext()

+                    .getResource("/" + pathToLib);

+            return (url != null);

+        }

+        catch (MalformedURLException e)

+        {

+            return false;

+        }

+    }

+

+    protected String getLocalePrefixForLocateResource()

+    {

+        String localePrefix = null;

+        FacesContext context = FacesContext.getCurrentInstance();

+

+        String bundleName = context.getApplication().getMessageBundle();

+

+        if (null != bundleName)

+        {

+            Locale locale = context.getApplication().getViewHandler()

+                    .calculateLocale(context);

+

+            ResourceBundle bundle = ResourceBundle.getBundle(bundleName,

+                    locale, getContextClassLoader());

+

+            if (bundle != null)

+            {

+                try

+                {

+                    localePrefix = bundle

+                            .getString(ResourceHandler.LOCALE_PREFIX);

+                }

+                catch (MissingResourceException e)

+                {

+                    // Ignore it and return null

+                }

+            }

+        }

+        return localePrefix;

+    }

+

+    /**

+     * Gets the ClassLoader associated with the current thread.  Includes a check for priviledges

+     * against java2 security to ensure no security related exceptions are encountered.

+     *

+     * @return ClassLoader

+     * @since 3.0.6

+     */

+    private static ClassLoader getContextClassLoader()

+    {

+        if (System.getSecurityManager() != null)

+        {

+            try

+            {

+                ClassLoader cl = AccessController

+                        .doPrivileged(new PrivilegedExceptionAction<ClassLoader>()

+                        {

+                            public ClassLoader run()

+                                    throws PrivilegedActionException

+                            {

+                                return Thread.currentThread()

+                                        .getContextClassLoader();

+                            }

+                        });

+                return cl;

+            }

+            catch (PrivilegedActionException pae)

+            {

+                throw new FacesException(pae);

+            }

+        }

+        else

+        {

+            return Thread.currentThread().getContextClassLoader();

+        }

+    }

+

+    private String getLibraryVersion(String path)

+    {

+        ExternalContext context = FacesContext.getCurrentInstance()

+                .getExternalContext();

+

+        String libraryVersion = null;

+        Set<String> libraryPaths = context.getResourcePaths("/" + path);

+        if (null != libraryPaths && !libraryPaths.isEmpty())

+        {

+            // Look in the libraryPaths for versioned libraries.

+            // If one or more versioned libraries are found, take

+            // the one with the "highest" version number as the value

+            // of libraryVersion. If no versioned libraries

+            // are found, let libraryVersion remain null.

+

+            for (String libraryPath : libraryPaths)

+            {

+                String version = libraryPath.substring(path.length());

+

+                if (VERSION_CHECKER.matcher(version).matches())

+                {

+                    version = version.substring(1, version.length() - 1);

+                    if (libraryVersion == null)

+                    {

+                        libraryVersion = version;

+                    }

+                    else if (compareVersion(libraryVersion, version) < 0)

+                    {

+                        libraryVersion = version;

+                    }

+                }

+            }

+        }

+        return libraryVersion;

+    }

+

+    private int compareVersion(String s1, String s2)

+    {

+        int n1 = 0;

+        int n2 = 0;

+        String o1 = s1;

+        String o2 = s2;

+

+        boolean p1 = true;

+        boolean p2 = true;

+

+        while (n1 == n2 && (p1 || p2))

+        {

+            int i1 = o1.indexOf('_');

+            int i2 = o2.indexOf('_');

+            if (i1 < 0)

+            {

+                if (o1.length() > 0)

+                {

+                    p1 = false;

+                    n1 = Integer.valueOf(o1);

+                    o1 = "";

+                }

+                else

+                {

+                    p1 = false;

+                    n1 = 0;

+                }

+            }

+            else

+            {

+                n1 = Integer.valueOf(o1.substring(0, i1));

+                o1 = o1.substring(i1 + 1);

+            }

+            if (i2 < 0)

+            {

+                if (o2.length() > 0)

+                {

+                    p2 = false;

+                    n2 = Integer.valueOf(o2);

+                    o2 = "";

+                }

+                else

+                {

+                    p2 = false;

+                    n2 = 0;

+                }

+            }

+            else

+            {

+                n2 = Integer.valueOf(o2.substring(0, i2));

+                o2 = o2.substring(i2 + 1);

+            }

+        }

+

+        if (n1 == n2)

+        {

+            return s1.length() - s2.length();

+        }

+        return n1 - n2;

+    }

+

+    private String getResourceVersion(String path)

+    {

+        ExternalContext context = FacesContext.getCurrentInstance()

+                .getExternalContext();

+        String resourceVersion = null;

+        Set<String> resourcePaths = context.getResourcePaths("/" + path);

+

+        if (null != resourcePaths && !resourcePaths.isEmpty())

+        {

+            // resourceVersion = // execute the comment

+            // Look in the resourcePaths for versioned resources.

+            // If one or more versioned resources are found, take

+            // the one with the "highest" version number as the value

+            // of resourceVersion. If no versioned libraries

+            // are found, let resourceVersion remain null.

+            for (String resourcePath : resourcePaths)

+            {

+                String version = resourcePath.substring(path.length());

+

+                if (RESOURCE_VERSION_CHECKER.matcher(version).matches())

+                {

+                    version = version.substring(1, version.lastIndexOf('.'));

+                    if (resourceVersion == null)

+                    {

+                        resourceVersion = version;

+                    }

+                    else if (compareVersion(resourceVersion, version) < 0)

+                    {

+                        resourceVersion = version;

+                    }

+                }

+            }

+        }

+        return resourceVersion;

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/visit/FullVisitContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/visit/FullVisitContext.java
new file mode 100644
index 0000000..c092381
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/visit/FullVisitContext.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.myfaces.test.mock.visit;

+

+import java.util.Collection;

+import java.util.Collections;

+import java.util.EnumSet;

+import java.util.Set;

+

+import javax.faces.component.NamingContainer;

+import javax.faces.component.UIComponent;

+import javax.faces.component.visit.VisitCallback;

+import javax.faces.component.visit.VisitContext;

+import javax.faces.component.visit.VisitHint;

+import javax.faces.component.visit.VisitResult;

+import javax.faces.context.FacesContext;

+

+/**

+ * <p>A VisitContext implementation that is

+ * used when performing a full component tree visit.</p>

+ * 

+ * @author Werner Punz, Blake Sullivan (latest modification by $Author: matzew $)

+ * @version $Rev: 885739 $ $Date: 2009-12-01 06:27:24 -0500 (Mar, 01 Dic 2009) $

+ * @since 1.0.0

+ */

+public class FullVisitContext extends VisitContext

+{

+

+    /**

+     * Creates a FullVisitorContext instance.

+     * @param facesContext the FacesContext for the current request

+     * @throws NullPointerException  if {@code facesContext}

+     *                               is {@code null}

+     */

+    public FullVisitContext(FacesContext facesContext)

+    {

+        this(facesContext, null);

+    }

+

+    /**

+     * Creates a FullVisitorContext instance with the specified

+     * hints.

+     *

+     * @param facesContext the FacesContext for the current request

+     * @param hints a the VisitHints for this visit

+     * @param phaseId PhaseId, if any that visit is ocurring under

+     * @throws NullPointerException  if {@code facesContext}

+     *                               is {@code null}

+     * @throws IllegalArgumentException if the phaseId is specified and

+     * hints does not contain VisitHint.EXECUTE_LIFECYCLE

+     */

+    public FullVisitContext(FacesContext facesContext, Set<VisitHint> hints)

+    {

+        if (facesContext == null)

+        {

+            throw new NullPointerException();

+        }

+

+        _facesContext = facesContext;

+

+        // Copy and store hints - ensure unmodifiable and non-empty

+        EnumSet<VisitHint> hintsEnumSet = ((hints == null) || (hints.isEmpty())) ? EnumSet

+                .noneOf(VisitHint.class)

+                : EnumSet.copyOf(hints);

+

+        _hints = Collections.unmodifiableSet(hintsEnumSet);

+    }

+

+    /**

+     * @see VisitContext#getFacesContext VisitContext.getFacesContext()

+     */

+    @Override

+    public FacesContext getFacesContext()

+    {

+        return _facesContext;

+    }

+

+    /**

+     * @see VisitContext#getIdsToVisit VisitContext.getIdsToVisit()

+     */

+    @Override

+    public Collection<String> getIdsToVisit()

+    {

+        // We always visits all ids

+        return ALL_IDS;

+    }

+

+    /**

+     * @see VisitContext#getSubtreeIdsToVisit VisitContext.getSubtreeIdsToVisit()

+     */

+    @Override

+    public Collection<String> getSubtreeIdsToVisit(UIComponent component)

+    {

+        // Make sure component is a NamingContainer

+        if (!(component instanceof NamingContainer))

+        {

+            throw new IllegalArgumentException(

+                    "Component is not a NamingContainer: " + component);

+        }

+

+        // We always visits all ids

+        return ALL_IDS;

+    }

+

+    /**

+     * @see VisitContext#getHints VisitContext.getHints

+     */

+    @Override

+    public Set<VisitHint> getHints()

+    {

+        return _hints;

+    }

+

+    /**

+     * @see VisitContext#invokeVisitCallback VisitContext.invokeVisitCallback()

+     */

+    @Override

+    public VisitResult invokeVisitCallback(UIComponent component,

+            VisitCallback callback)

+    {

+        // Nothing interesting here - just invoke the callback.

+        // (PartialVisitContext.invokeVisitCallback() does all of the 

+        // interesting work.)

+        return callback.visit(this, component);

+    }

+

+    // The FacesContext for this request

+    private final FacesContext _facesContext;

+

+    // Our visit hints

+    private final Set<VisitHint> _hints;

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.java b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.java
new file mode 100644
index 0000000..052e38a
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.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.myfaces.test.mock.visit;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitResult;
+
+/**
+ * <p>Mock implementation of <code>VisitCallback</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockVisitCallback implements VisitCallback
+{
+
+    public VisitResult visit(VisitContext context, UIComponent target)
+    {
+        return VisitResult.ACCEPT;
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.java
new file mode 100644
index 0000000..7e5b43c
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.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.myfaces.test.mock.visit;
+
+import javax.faces.component.NamingContainer;
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitHint;
+import javax.faces.component.visit.VisitResult;
+import javax.faces.context.FacesContext;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * <p>Mock implementation of <code>VisitContext</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockVisitContext extends VisitContext
+{
+
+    private final FacesContext _facesContext;
+    private final Set<VisitHint> _hints;
+
+    public MockVisitContext(FacesContext facesContext)
+    {
+        this(facesContext, null);
+    }
+
+    public MockVisitContext(FacesContext facesContext, Set<VisitHint> hints)
+    {
+        if (facesContext == null)
+        {
+            throw new NullPointerException();
+        }
+
+        _facesContext = facesContext;
+
+        // Copy and store hints - ensure unmodifiable and non-empty
+        EnumSet<VisitHint> hintsEnumSet = ((hints == null) || (hints.isEmpty())) ? EnumSet
+                .noneOf(VisitHint.class)
+                : EnumSet.copyOf(hints);
+
+        _hints = Collections.unmodifiableSet(hintsEnumSet);
+    }
+
+    @Override
+    public FacesContext getFacesContext()
+    {
+        return _facesContext;
+    }
+
+    @Override
+    public Set<VisitHint> getHints()
+    {
+        return _hints;
+    }
+
+    @Override
+    public Collection<String> getIdsToVisit()
+    {
+        return ALL_IDS;
+    }
+
+    @Override
+    public Collection<String> getSubtreeIdsToVisit(UIComponent component)
+    {
+        // Make sure component is a NamingContainer
+        if (!(component instanceof NamingContainer))
+        {
+            throw new IllegalArgumentException(
+                    "Component is not a NamingContainer: " + component);
+        }
+
+        return ALL_IDS;
+    }
+
+    @Override
+    public VisitResult invokeVisitCallback(UIComponent component,
+            VisitCallback callback)
+    {
+        return callback.visit(this, component);
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.java b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.java
new file mode 100644
index 0000000..8d48013
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.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.myfaces.test.mock.visit;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitContextFactory;
+import javax.faces.component.visit.VisitHint;
+import javax.faces.context.FacesContext;
+
+/**
+ * <p>Mock implementation of <code>VisitContextFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 1.0.0
+ */
+public class MockVisitContextFactory extends VisitContextFactory
+{
+
+    @Override
+    public VisitContext getVisitContext(FacesContext context,
+            Collection<String> ids, Set<VisitHint> hints)
+    {
+        if (ids == null || ids.isEmpty())
+        {
+            return new FullVisitContext(context, hints);
+        }
+        else
+        {
+            return new PartialVisitContext(context, ids, hints);
+        }
+    }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/mock/visit/PartialVisitContext.java b/test30/src/main/java/org/apache/myfaces/test/mock/visit/PartialVisitContext.java
new file mode 100644
index 0000000..54e04c0
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/mock/visit/PartialVisitContext.java
@@ -0,0 +1,469 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.mock.visit;

+

+import java.util.AbstractCollection;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.Collections;

+import java.util.EnumSet;

+import java.util.HashMap;

+import java.util.HashSet;

+import java.util.Iterator;

+import java.util.Map;

+import java.util.Set;

+

+import javax.faces.component.NamingContainer;

+import javax.faces.component.UIComponent;

+import javax.faces.component.UINamingContainer;

+import javax.faces.component.visit.VisitCallback;

+import javax.faces.component.visit.VisitContext;

+import javax.faces.component.visit.VisitHint;

+import javax.faces.component.visit.VisitResult;

+import javax.faces.context.FacesContext;

+

+/**

+ * <p>A VisitContext implementation that is

+ * used when performing a partial component tree visit.</p>

+ * 

+ * @author Werner Punz, Blake Sullivan (latest modification by $Author: lu4242 $)

+ * @version $Rev: 949094 $ $Date: 2010-05-27 22:59:10 -0500 (Jue, 27 May 2010) $

+ * @since 1.0.0

+ */

+public class PartialVisitContext extends VisitContext

+{

+

+    /**

+     * Creates a PartialVisitorContext instance.

+     * @param facesContext the FacesContext for the current request

+     * @param clientIds the client ids of the components to visit

+     * @throws NullPointerException  if {@code facesContext}

+     *                               is {@code null}

+     */

+    public PartialVisitContext(FacesContext facesContext,

+            Collection<String> clientIds)

+    {

+        this(facesContext, clientIds, null);

+    }

+

+    /**

+     * Creates a PartialVisitorContext instance with the specified hints.

+     * @param facesContext the FacesContext for the current request

+     * @param clientIds the client ids of the components to visit

+     * @param hints a the VisitHints for this visit

+     * @throws NullPointerException  if {@code facesContext}

+     *                               is {@code null}

+     * @throws IllegalArgumentException if the phaseId is specified and

+     * hints does not contain VisitHint.EXECUTE_LIFECYCLE

+     */

+    public PartialVisitContext(FacesContext facesContext,

+            Collection<String> clientIds, Set<VisitHint> hints)

+    {

+        if (facesContext == null)

+        {

+            throw new NullPointerException();

+        }

+

+        _facesContext = facesContext;

+

+        // Copy the client ids into a HashSet to allow for quick lookups.

+        Set<String> clientIdSet = (clientIds == null) ? new HashSet<String>()

+                : new HashSet<String>(clientIds);

+

+        // Initialize our various collections

+        // We maintain 4 collections:

+        //

+        // 1. clientIds: contains all of the client ids to visit

+        // 2. ids: contains just ids (not client ids) to visit.

+        //    We use this to optimize our check to see whether a

+        //    particular component is in the visit set (ie. to

+        //    avoid having to compute the client id).

+        // 3. subtreeClientIds: contains client ids to visit broken

+        //    out by naming container subtree.  (Needed by

+        //    getSubtreeIdsToVisit()).

+        // 4. unvisitedClientIds: contains the client ids to visit that

+        //    have not yet been visited.

+        //

+        // We populate these now.

+        //

+        // Note that we use default HashSet/Map initial capacities, though

+        // perhaps we could pick more intelligent defaults.

+

+        // Initialize unvisitedClientIds collection

+        _unvisitedClientIds = new HashSet<String>();

+

+        // Initialize ids collection

+        _ids = new HashSet<String>();

+

+        // Intialize subtreeClientIds collection

+        _subtreeClientIds = new HashMap<String, Collection<String>>();

+

+        // Initialize the clientIds collection.  Note that we proxy 

+        // this collection so that we can trap adds/removes and sync 

+        // up all of the other collections.

+        _clientIds = new CollectionProxy<String>(new HashSet<String>());

+

+        // Finally, populate the clientIds collection.  This has the

+        // side effect of populating all of the other collections.       

+        _clientIds.addAll(clientIdSet);

+

+        // Copy and store hints - ensure unmodifiable and non-empty

+        EnumSet<VisitHint> hintsEnumSet = ((hints == null) || (hints.isEmpty())) ? EnumSet

+                .noneOf(VisitHint.class)

+                : EnumSet.copyOf(hints);

+

+        _hints = Collections.unmodifiableSet(hintsEnumSet);

+    }

+

+    /**

+     * @see VisitContext#getFacesContext VisitContext.getFacesContext()

+     */

+    @Override

+    public FacesContext getFacesContext()

+    {

+        return _facesContext;

+    }

+

+    /**

+     * @see VisitContext#getHints VisitContext.getHints

+     */

+    @Override

+    public Set<VisitHint> getHints()

+    {

+        return _hints;

+    }

+

+    /**

+     * @see VisitContext#getIdsToVisit VisitContext.getIdsToVisit()

+     */

+    @Override

+    public Collection<String> getIdsToVisit()

+    {

+        // We just return our clientIds collection.  This is

+        // the modifiable (but proxied) collection of all of

+        // the client ids to visit.

+        return _clientIds;

+    }

+

+    /**

+     * @see VisitContext#getSubtreeIdsToVisit VisitContext.getSubtreeIdsToVisit()

+     */

+    @Override

+    public Collection<String> getSubtreeIdsToVisit(UIComponent component)

+    {

+        // Make sure component is a NamingContainer

+        if (!(component instanceof NamingContainer))

+        {

+            throw new IllegalArgumentException(

+                    "Component is not a NamingContainer: " + component);

+        }

+

+        String clientId = component.getClientId(getFacesContext());

+        Collection<String> ids = _subtreeClientIds.get(clientId);

+

+        if (ids == null)

+        {

+            return Collections.emptyList();

+        }

+        else

+        {

+            return Collections.unmodifiableCollection(ids);

+        }

+    }

+

+    /**

+     * @see VisitContext#invokeVisitCallback VisitContext.invokeVisitCallback()

+     */

+    @Override

+    public VisitResult invokeVisitCallback(UIComponent component,

+            VisitCallback callback)

+    {

+        // First sure that we should visit this component - ie.

+        // that this component is represented in our id set.

+        String clientId = _getVisitId(component);

+

+        if (clientId == null)

+        {

+            // Not visiting this component, but allow visit to

+            // continue into this subtree in case we've got

+            // visit targets there.

+            return VisitResult.ACCEPT;

+        }

+

+        // If we made it this far, the component matches one of

+        // client ids, so perform the visit.

+        VisitResult result = callback.visit(this, component);

+

+        // Remove the component from our "unvisited" collection

+        _unvisitedClientIds.remove(clientId);

+

+        // If the unvisited collection is now empty, we are done.

+        // Return VisitResult.COMPLETE to terminate the visit.

+        if (_unvisitedClientIds.isEmpty())

+        {

+            return VisitResult.COMPLETE;

+        }

+        else

+        {

+            // Otherwise, just return the callback's result 

+            return result;

+        }

+    }

+

+    // Called by CollectionProxy to notify PartialVisitContext that

+    // an new id has been added.

+    private void _idAdded(String clientId)

+    {

+        // An id to visit has been added, update our other

+        // collections to reflect this.

+

+        // Update the ids collection

+        _ids.add(_getIdFromClientId(clientId));

+

+        // Update the unvisited ids collection

+        _unvisitedClientIds.add(clientId);

+

+        // Update the subtree ids collection

+        _addSubtreeClientId(clientId);

+    }

+

+    // Called by CollectionProxy to notify PartialVisitContext that

+    // an id has been removed

+    private void _idRemoved(String clientId)

+    {

+        // An id to visit has been removed, update our other

+        // collections to reflect this.  Note that we don't

+        // update the ids collection, since we ids (non-client ids)

+        // may not be unique.

+

+        // Update the unvisited ids collection

+        _unvisitedClientIds.remove(clientId);

+

+        // Update the subtree ids collection

+        _removeSubtreeClientId(clientId);

+    }

+

+    // Tests whether the specified component should be visited.

+    // If so, returns its client id.  If not, returns null.

+    private String _getVisitId(UIComponent component)

+    {

+        // We first check to see whether the component's id

+        // is in our id collection.  We do this before checking

+        // for the full client id because getting the full client id

+        // is more expensive than just getting the local id.

+        String id = component.getId();

+

+        if ((id != null) && !_ids.contains(id))

+        {

+            return null;

+        }

+

+        // The id was a match - now check the client id.

+        // note that client id should never be null (should be

+        // generated even if id is null, so asserting this.)

+        String clientId = component.getClientId(getFacesContext());

+        assert (clientId != null);

+

+        return _clientIds.contains(clientId) ? clientId : null;

+    }

+

+    // Converts an client id into a plain old id by ripping

+    // out the trailing id segmetn.

+    private String _getIdFromClientId(String clientId)

+    {

+        final char separator = UINamingContainer

+                .getSeparatorChar(_facesContext);

+        int lastIndex = clientId.lastIndexOf(separator);

+

+        String id = null;

+

+        if (lastIndex < 0)

+        {

+            id = clientId;

+        }

+        else if (lastIndex < (clientId.length() - 1))

+        {

+            id = clientId.substring(lastIndex + 1);

+        }

+        else

+        {

+            // TODO log warning for trailing colon case

+        }

+

+        return id;

+    }

+

+    // Given a single client id, populate the subtree map with all possible

+    // subtree client ids

+    private void _addSubtreeClientId(String clientId)

+    {

+        // Loop over the client id and find the substring corresponding to

+        // each ancestor NamingContainer client id.  For each ancestor

+        // NamingContainer, add an entry into the map for the full client

+        // id.

+        final char separator = UINamingContainer

+                .getSeparatorChar(_facesContext);

+

+        int length = clientId.length();

+

+        for (int i = 0; i < length; i++)

+        {

+            if (clientId.charAt(i) == separator)

+            {

+                // We found an ancestor NamingContainer client id - add 

+                // an entry to the map.

+                String namingContainerClientId = clientId.substring(0, i);

+

+                // Check to see whether we've already ids under this

+                // NamingContainer client id.  If not, create the 

+                // Collection for this NamingContainer client id and

+                // stash it away in our map

+                Collection<String> c = _subtreeClientIds

+                        .get(namingContainerClientId);

+

+                if (c == null)

+                {

+                    // TODO: smarter initial size?

+                    c = new ArrayList<String>();

+                    _subtreeClientIds.put(namingContainerClientId, c);

+                }

+

+                // Stash away the client id

+                c.add(clientId);

+            }

+        }

+    }

+

+    // Given a single client id, remove any entries corresponding

+    // entries from our subtree collections

+    private void _removeSubtreeClientId(String clientId)

+    {

+        // Loop through each entry in the map and check to see whether

+        // the client id to remove should be contained in the corresponding

+        // collection - ie. whether the key (the NamingContainer client id)

+        // is present at the start of the client id to remove.

+        for (String key : _subtreeClientIds.keySet())

+        {

+            if (clientId.startsWith(key))

+            {

+                // If the clientId starts with the key, we should

+                // have an entry for this clientId in the corresponding

+                // collection.  Remove it.

+                Collection<String> ids = _subtreeClientIds.get(key);

+                ids.remove(clientId);

+            }

+        }

+    }

+

+    // Little proxy collection implementation.  We proxy the id

+    // collection so that we can detect modifications and update

+    // our internal state when ids to visit are added or removed.

+    private class CollectionProxy<E extends String> extends

+            AbstractCollection<E>

+    {

+        private CollectionProxy(Collection<E> wrapped)

+        {

+            _wrapped = wrapped;

+        }

+

+        @Override

+        public int size()

+        {

+            return _wrapped.size();

+        }

+

+        @Override

+        public Iterator<E> iterator()

+        {

+            return new IteratorProxy<E>(_wrapped.iterator());

+        }

+

+        @Override

+        public boolean add(E o)

+        {

+            boolean added = _wrapped.add(o);

+

+            if (added)

+            {

+                _idAdded(o);

+            }

+

+            return added;

+        }

+

+        private final Collection<E> _wrapped;

+    }

+

+    // Little proxy iterator implementation used by CollectionProxy

+    // so that we can catch removes.

+    private class IteratorProxy<E extends String> implements Iterator<E>

+    {

+        private IteratorProxy(Iterator<E> wrapped)

+        {

+            _wrapped = wrapped;

+        }

+

+        public boolean hasNext()

+        {

+            return _wrapped.hasNext();

+        }

+

+        public E next()

+        {

+            _current = _wrapped.next();

+

+            return _current;

+        }

+

+        public void remove()

+        {

+            if (_current != null)

+            {

+                _idRemoved(_current);

+            }

+

+            _wrapped.remove();

+        }

+

+        private final Iterator<E> _wrapped;

+

+        private E _current = null;

+    }

+

+    // The client ids to visit

+    private final Collection<String> _clientIds;

+

+    // The ids to visit

+    private final Collection<String> _ids;

+

+    // The client ids that have yet to be visited

+    private final Collection<String> _unvisitedClientIds;

+

+    // This map contains the information needed by getIdsToVisit().

+    // The keys in this map are NamingContainer client ids.  The values

+    // are collections containing all of the client ids to visit within

+    // corresponding naming container.

+    private final Map<String, Collection<String>> _subtreeClientIds;

+

+    // The FacesContext for this request

+    private final FacesContext _facesContext;

+

+    // Our visit hints

+    private final Set<VisitHint> _hints;

+}
\ No newline at end of file
diff --git a/test30/src/main/java/org/apache/myfaces/test/runners/NamedRunner.java b/test30/src/main/java/org/apache/myfaces/test/runners/NamedRunner.java
new file mode 100644
index 0000000..4f9502e
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/runners/NamedRunner.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.myfaces.test.runners;

+

+import org.junit.runner.notification.RunNotifier;

+import org.junit.runners.BlockJUnit4ClassRunner;

+import org.junit.runners.model.FrameworkMethod;

+import org.junit.runners.model.InitializationError;

+

+/**

+ * A Junit 4 runner that stores the name of the method that runs.  The methods marked with @After and @Before 

+ * (but also the Test method itself) can request this name for which it is running.

+ * 

+ * @author Rudy De Busscher

+ * @since 1.0.0

+ */

+public class NamedRunner extends BlockJUnit4ClassRunner

+{

+

+    /** The Constant PREFIX_KEY. */

+    private static final String PREFIX_KEY = "ClassLoaderRunner_TestMethodName_";

+

+    /** The Constant NO_NAME. */

+    private static final String NO_NAME = "null";

+

+    /**

+     * Instantiates a new named runner.

+     * 

+     * @param klass the klass

+     * 

+     * @throws InitializationError the initialization error

+     */

+    public NamedRunner(Class<?> klass) throws InitializationError

+    {

+        super(klass);

+    }

+

+    @Override

+    protected void runChild(FrameworkMethod method, RunNotifier notifier)

+

+    {

+        storeTestMethodName(method.getName());

+        super.runChild(method, notifier);

+        removeTestMethodName();

+    }

+

+    /**

+     * Gets the test method name.

+     * 

+     * @return the test method name

+     */

+    public static String getTestMethodName()

+    {

+        return retrieveTestMethodName();

+    }

+

+    /**

+     * Retrieve test method name.

+     * 

+     * @return the string

+     */

+    private static String retrieveTestMethodName()

+    {

+        // We can't use a ThreadLocal variable in the case the TestPerClassLoader runner is used.  Then this

+        // Method is accessed from another classloader and thus reinitialized variables.

+        String result = null;

+        String storedName = System.getProperty(getKey());

+        if (!NO_NAME.equals(storedName))

+        {

+            result = storedName;

+        }

+        return result;

+    }

+

+    /**

+     * Removes the test method name.

+     */

+    private static void removeTestMethodName()

+    {

+        // We can't use a ThreadLocal variable in the case the TestPerClassLoader runner is used.  Then this

+        // Method is accessed from another classloader and thus reinitialized variables.

+        System.setProperty(getKey(), NO_NAME);

+

+    }

+

+    /**

+     * Store test method name.

+     * 

+     * @param name the name

+     */

+    private static void storeTestMethodName(String name)

+    {

+

+        // We can't use a ThreadLocal variable in the case the TestPerClassLoader runner is used.  Then this

+        // Method is accessed from another classloader and thus reinitialized variables.

+        System.setProperty(getKey(), name);

+    }

+

+    /**

+     * Gets the key.

+     * 

+     * @return the key

+     */

+    private static String getKey()

+    {

+        StringBuffer buffer = new StringBuffer();

+        buffer.append(PREFIX_KEY).append(Thread.currentThread().getName());

+        return buffer.toString();

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/runners/TestClassLoader.java b/test30/src/main/java/org/apache/myfaces/test/runners/TestClassLoader.java
new file mode 100644
index 0000000..86ee8fe
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/runners/TestClassLoader.java
@@ -0,0 +1,359 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.myfaces.test.runners;

+

+import java.io.ByteArrayOutputStream;

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.URL;

+import java.util.Enumeration;

+import java.util.Properties;

+import java.util.StringTokenizer;

+import java.util.Vector;

+import java.util.zip.ZipEntry;

+import java.util.zip.ZipFile;

+

+/**

+ * A Classloader that delegates the loading of the classes to the parent if it is in the 

+ * excluded list or does it itself otherwise.

+ * Useful for a system that sets up a classloader per Test system.

+ *  

+ * @author Rudy De Busscher

+ * @since 1.0.0

+ */

+public class TestClassLoader extends ClassLoader

+{

+    /** scanned class path */

+    private Vector<String> fPathItems;

+    /** default excluded paths */

+    private String[] defaultExclusions = { "junit.framework.",

+            "junit.extensions.", "junit.runner." };

+    /** name of excluded properties file */

+    static final String EXCLUDED_FILE = "excluded.properties";

+    /** excluded paths */

+    private Vector<String> fExcluded;

+

+    /**

+     * Constructs a TestCaseLoader. It scans the class path

+     * and the excluded package paths

+     */

+    public TestClassLoader()

+    {

+        this(System.getProperty("java.class.path"));

+    }

+

+    /**

+     * Constructs a TestCaseLoader. It scans the class path

+     * and the excluded package paths

+     */

+    public TestClassLoader(String classPath)

+    {

+        scanPath(classPath);

+        readExcludedPackages();

+    }

+

+    private void scanPath(String classPath)

+    {

+        String separator = System.getProperty("path.separator");

+        fPathItems = new Vector<String>(10);

+        StringTokenizer st = new StringTokenizer(classPath, separator);

+        while (st.hasMoreTokens())

+        {

+            fPathItems.addElement(st.nextToken());

+        }

+    }

+

+    @Override

+    public URL getResource(String name)

+    {

+        return ClassLoader.getSystemResource(name);

+    }

+

+    @Override

+    public InputStream getResourceAsStream(String name)

+    {

+        return ClassLoader.getSystemResourceAsStream(name);

+    }

+

+    /**

+     * Checks if path is excluded.

+     * 

+     * @param name the name

+     * 

+     * @return true, if is excluded

+     */

+    public boolean isExcluded(String name)

+    {

+        for (int i = 0; i < fExcluded.size(); i++)

+        {

+            if (name.startsWith((String) fExcluded.elementAt(i)))

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+

+    @Override

+    public synchronized Class loadClass(String name, boolean resolve)

+            throws ClassNotFoundException

+    {

+

+        Class c = findLoadedClass(name);

+        if (c != null)

+        {

+            return c;

+        }

+        //

+        // Delegate the loading of excluded classes to the

+        // standard class loader.

+        //

+        if (isExcluded(name))

+        {

+            try

+            {

+                c = findSystemClass(name);

+                return c;

+            }

+            catch (ClassNotFoundException e)

+            {

+                // keep searching

+            }

+        }

+        if (c == null)

+        {

+            byte[] data = lookupClassData(name);

+            if (data == null)

+            {

+                throw new ClassNotFoundException();

+            }

+            c = defineClass(name, data, 0, data.length);

+        }

+        if (resolve)

+        {

+            resolveClass(c);

+        }

+        return c;

+    }

+

+    /**

+     * Lookup class data in one of the classpath elements configured.

+     * 

+     * @param className the class name

+     * 

+     * @return the bytes making up the class

+     * 

+     * @throws ClassNotFoundException when class can't be found on the classpath elements.

+     */

+    private byte[] lookupClassData(String className)

+            throws ClassNotFoundException

+    {

+        byte[] data = null;

+        for (int i = 0; i < fPathItems.size(); i++)

+        {

+            String path = (String) fPathItems.elementAt(i);

+            String fileName = className.replace('.', '/') + ".class";

+            if (isJar(path))

+            {

+                data = loadJarData(path, fileName);

+            }

+            else

+            {

+                data = loadFileData(path, fileName);

+            }

+            if (data != null)

+            {

+                return data;

+            }

+        }

+        throw new ClassNotFoundException(className);

+    }

+

+    /**

+     * Checks if is jar.

+     * 

+     * @param pathEntry the path entry

+     * 

+     * @return true, if is jar

+     */

+    boolean isJar(String pathEntry)

+    {

+        return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip");

+    }

+

+    /**

+     * Load class bytes from file.

+     * 

+     * @param path the path

+     * @param fileName the file name

+     * 

+     * @return the bytes making up the class

+     */

+    private byte[] loadFileData(String path, String fileName)

+    {

+        File file = new File(path, fileName);

+        if (file.exists())

+        {

+            return getClassData(file);

+        }

+        return null;

+    }

+

+    private byte[] getClassData(File f)

+    {

+        try

+        {

+            FileInputStream stream = new FileInputStream(f);

+            ByteArrayOutputStream out = new ByteArrayOutputStream(1000);

+            byte[] b = new byte[1000];

+            int n;

+            while ((n = stream.read(b)) != -1)

+            {

+                out.write(b, 0, n);

+            }

+            stream.close();

+            out.close();

+            return out.toByteArray();

+

+        }

+        catch (IOException e)

+        {

+        }

+        return null;

+    }

+

+    /**

+     * Load class bytes from jar.

+     * 

+     * @param path the path

+     * @param fileName the file name

+     * 

+     * @return the bytes making up the class

+     */

+    private byte[] loadJarData(String path, String fileName)

+    {

+        ZipFile zipFile = null;

+        InputStream stream = null;

+        File archive = new File(path);

+        if (!archive.exists())

+        {

+            return null;

+        }

+        try

+        {

+            zipFile = new ZipFile(archive);

+        }

+        catch (IOException io)

+        {

+            return null;

+        }

+        ZipEntry entry = zipFile.getEntry(fileName);

+        if (entry == null)

+        {

+            return null;

+        }

+        int size = (int) entry.getSize();

+        try

+        {

+            stream = zipFile.getInputStream(entry);

+            byte[] data = new byte[size];

+            int pos = 0;

+            while (pos < size)

+            {

+                int n = stream.read(data, pos, data.length - pos);

+                pos += n;

+            }

+            zipFile.close();

+            return data;

+        }

+        catch (IOException e)

+        {

+        }

+        finally

+        {

+            try

+            {

+                if (stream != null)

+                {

+                    stream.close();

+                }

+            }

+            catch (IOException e)

+            {

+            }

+        }

+        return null;

+    }

+

+    /**

+     * Read excluded packages for which classes are read by the parent class loader.

+     */

+    private void readExcludedPackages()

+    {

+        fExcluded = new Vector<String>(10);

+        for (int i = 0; i < defaultExclusions.length; i++)

+        {

+            fExcluded.addElement(defaultExclusions[i]);

+        }

+

+        InputStream is = getClass().getResourceAsStream(EXCLUDED_FILE);

+        if (is == null)

+        {

+            return;

+        }

+        Properties p = new Properties();

+        try

+        {

+            p.load(is);

+        }

+        catch (IOException e)

+        {

+            return;

+        }

+        finally

+        {

+            try

+            {

+                is.close();

+            }

+            catch (IOException e)

+            {

+            }

+        }

+        for (Enumeration e = p.propertyNames(); e.hasMoreElements();)

+        {

+            String key = (String) e.nextElement();

+            if (key.startsWith("excluded."))

+            {

+                String path = p.getProperty(key);

+                path = path.trim();

+                if (path.endsWith("*"))

+                {

+                    path = path.substring(0, path.length() - 1);

+                }

+                if (path.length() > 0)

+                {

+                    fExcluded.addElement(path);

+                }

+            }

+        }

+    }

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/runners/TestPerClassLoaderRunner.java b/test30/src/main/java/org/apache/myfaces/test/runners/TestPerClassLoaderRunner.java
new file mode 100644
index 0000000..8f2d047
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/runners/TestPerClassLoaderRunner.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.myfaces.test.runners;

+

+import java.io.File;

+import java.io.IOException;

+import java.lang.annotation.Annotation;

+import java.lang.reflect.Method;

+import java.net.MalformedURLException;

+import java.util.List;

+import java.util.StringTokenizer;

+import java.util.Vector;

+import java.util.jar.Attributes;

+import java.util.jar.Manifest;

+import java.util.logging.Logger;

+import java.util.zip.ZipEntry;

+import java.util.zip.ZipFile;

+

+import org.junit.After;

+import org.junit.Before;

+import org.junit.internal.runners.statements.Fail;

+import org.junit.internal.runners.statements.RunAfters;

+import org.junit.internal.runners.statements.RunBefores;

+import org.junit.runners.model.FrameworkMethod;

+import org.junit.runners.model.InitializationError;

+import org.junit.runners.model.Statement;

+import org.junit.runners.model.TestClass;

+

+/**

+ * A Junit 4 runner that executes each Test method with a new Custom classloader so 

+ * that all variables, 

+ * also the final ones, are reinitialized.

+ * 

+ * @author Rudy De Busscher

+ * @since 1.0.0

+ */

+public class TestPerClassLoaderRunner extends NamedRunner

+{

+    private static final Logger LOGGER = Logger

+            .getLogger(TestPerClassLoaderRunner.class.getName());

+

+    // The classpath is needed because the custom class loader looks there to find the classes.

+    private static String classPath;

+    private static boolean classPathDetermined = false;

+

+    // Some data related to the class from the custom class loader.

+    private TestClass testClassFromClassLoader;

+    private Object beforeFromClassLoader;

+    private Object afterFromClassLoader;

+

+    /**

+     * Instantiates a new test per class loader runner.

+     * 

+     * @param klass the klass

+     * 

+     * @throws InitializationError the initialization error

+     */

+    public TestPerClassLoaderRunner(Class<?> klass) throws InitializationError

+    {

+        super(klass);

+    }

+

+    @Override

+    protected Object createTest() throws Exception

+    {

+        // Need an instance now from the class loaded by the custom loader.

+        return testClassFromClassLoader.getJavaClass().newInstance();

+    }

+

+    /**

+     * Load classes (TestCase, @Before and @After with custom class loader.

+     * 

+     * @throws ClassNotFoundException the class not found exception

+     */

+    private void loadClassesWithCustomClassLoader()

+            throws ClassNotFoundException

+    {

+        ClassLoader classLoader;

+

+        // We need the classpath so that our custom loader can search for the requested classes.

+        String testPath = getClassPath();

+        if (testPath != null)

+        {

+            classLoader = new TestClassLoader(testPath);

+        }

+

+        else

+        {

+            classLoader = new TestClassLoader();

+

+        }

+

+        Thread.currentThread().setContextClassLoader(classLoader);

+        testClassFromClassLoader = new TestClass(classLoader

+                .loadClass(getTestClass().getJavaClass().getName()));

+        // See withAfters and withBefores for the reason.

+        beforeFromClassLoader = classLoader.loadClass(Before.class.getName());

+        afterFromClassLoader = classLoader.loadClass(After.class.getName());

+    }

+

+    @Override

+    protected Statement methodBlock(FrameworkMethod method)

+    {

+        FrameworkMethod newMethod = null;

+        try

+        {

+            // Need the class frmo the custom loader now, so lets load the class.

+            loadClassesWithCustomClassLoader();

+            // The method as parameter is frmo the original class and thus not found in our

+            // class loaded by the custom name (reflection is class loader sensitive)

+            // So find the same method but now in the class frmo the class Loader.

+            Method methodFromNewlyLoadedClass = testClassFromClassLoader

+                    .getJavaClass().getMethod(method.getName());

+            newMethod = new FrameworkMethod(methodFromNewlyLoadedClass);

+        }

+        catch (ClassNotFoundException e)

+        {

+            // Show any problem nicely as a JUnit Test failure.

+            return new Fail(e);

+        }

+        catch (SecurityException e)

+        {

+            return new Fail(e);

+        }

+        catch (NoSuchMethodException e)

+        {

+            return new Fail(e);

+        }

+

+        // We can carry out the normal JUnit functionality with our newly discoverd method now. 

+        return super.methodBlock(newMethod);

+    }

+

+    @SuppressWarnings("unchecked")

+    @Override

+    protected Statement withAfters(FrameworkMethod method, Object target,

+            Statement statement)

+    {

+        // We now to need to search in the class from the custom loader.

+        //We also need to search with the annotation loaded by the custom class loader or otherwise 

+        // we don't find any method.

+        List<FrameworkMethod> afters = testClassFromClassLoader

+                .getAnnotatedMethods((Class<? extends Annotation>) afterFromClassLoader);

+        return new RunAfters(statement, afters, target);

+    }

+

+    @SuppressWarnings("unchecked")

+    @Override

+    protected Statement withBefores(FrameworkMethod method, Object target,

+            Statement statement)

+    {

+        // We now to need to search in the class from the custom loader.

+        //We also need to search with the annotation loaded by the custom class loader or otherwise 

+        // we don't find any method.

+        List<FrameworkMethod> befores = testClassFromClassLoader

+                .getAnnotatedMethods((Class<? extends Annotation>) beforeFromClassLoader);

+        return new RunBefores(statement, befores, target);

+    }

+

+    /**

+     * Gets the class path. This value is cached in a static variable for performance reasons.

+     * 

+     * @return the class path

+     */

+    private static String getClassPath()

+    {

+        if (classPathDetermined)

+        {

+            return classPath;

+        }

+

+        classPathDetermined = true;

+        // running from maven, we have the classpath in this property.

+        classPath = System.getProperty("surefire.test.class.path");

+        if (classPath != null)

+        {

+            return classPath;

+        }

+

+        // For a multi module project, running it from the top we have to find it using another way.

+        // We also need to set useSystemClassLoader=true in the POM so that we gets a jar with the classpath in it.

+        String booterClassPath = System.getProperty("java.class.path");

+        Vector<String> pathItems = null;

+        if (booterClassPath != null)

+        {

+            pathItems = scanPath(booterClassPath);

+        }

+        // Do we have just 1 entry as classpath which is a jar?

+        if (pathItems != null && pathItems.size() == 1

+                && isJar((String) pathItems.get(0)))

+        {

+            classPath = loadJarManifestClassPath((String) pathItems.get(0),

+                    "META-INF/MANIFEST.MF");

+        }

+        return classPath;

+

+    }

+

+    /**

+     * Load jar manifest class path.

+     * 

+     * @param path the path

+     * @param fileName the file name

+     * 

+     * @return the string

+     */

+    private static String loadJarManifestClassPath(String path, String fileName)

+    {

+        File archive = new File(path);

+        if (!archive.exists())

+        {

+            return null;

+        }

+        ZipFile zipFile = null;

+

+        try

+        {

+            zipFile = new ZipFile(archive);

+        }

+        catch (IOException io)

+        {

+            return null;

+        }

+

+        ZipEntry entry = zipFile.getEntry(fileName);

+        if (entry == null)

+        {

+            return null;

+        }

+        try

+        {

+            Manifest mf = new Manifest();

+            mf.read(zipFile.getInputStream(entry));

+

+            return mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH)

+                    .replaceAll(" ", System.getProperty("path.separator"))

+                    .replaceAll("file:/", "");

+        }

+        catch (MalformedURLException e)

+        {

+            LOGGER.throwing("ClassLoaderTestSuite", "loadJarManifestClassPath",

+                    e);

+        }

+        catch (IOException e)

+        {

+            LOGGER.throwing("ClassLoaderTestSuite", "loadJarManifestClassPath",

+                    e);

+        }

+        return null;

+    }

+

+    /**

+     * Checks if is jar.

+     * 

+     * @param pathEntry the path entry

+     * 

+     * @return true, if is jar

+     */

+    private static boolean isJar(String pathEntry)

+    {

+        return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip");

+    }

+

+    /**

+     * Scan path for all directories.

+     * 

+     * @param classPath the class path

+     * 

+     * @return the vector< string>

+     */

+    private static Vector<String> scanPath(String classPath)

+    {

+        String separator = System.getProperty("path.separator");

+        Vector<String> pathItems = new Vector<String>(10);

+        StringTokenizer st = new StringTokenizer(classPath, separator);

+        while (st.hasMoreTokens())

+        {

+            pathItems.addElement(st.nextToken());

+        }

+        return pathItems;

+    }

+

+}

diff --git a/test30/src/main/java/org/apache/myfaces/test/util/Jsf11Utils.java b/test30/src/main/java/org/apache/myfaces/test/util/Jsf11Utils.java
new file mode 100644
index 0000000..cb3c6ff
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/util/Jsf11Utils.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.myfaces.test.util;
+
+public class Jsf11Utils
+{
+
+  public static void throwViewExpiredException(String viewId)
+  {
+    throw new RuntimeException(
+            "The expected view was not returned for the view identifier: " + viewId);
+  }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/util/Jsf12Utils.java b/test30/src/main/java/org/apache/myfaces/test/util/Jsf12Utils.java
new file mode 100644
index 0000000..17d1803
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/util/Jsf12Utils.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.myfaces.test.util;
+
+import javax.faces.application.ViewExpiredException;
+import javax.faces.application.ViewHandler;
+import javax.faces.context.FacesContext;
+
+public class Jsf12Utils
+{
+
+  public static void initView(FacesContext facesContext, ViewHandler viewHandler)
+  {
+    viewHandler.initView(facesContext);
+  }
+
+  public static void throwViewExpiredException(String viewId)
+  {
+    throw new ViewExpiredException(
+        "The expected view was not returned for the view identifier: " + viewId, viewId);
+  }
+}
diff --git a/test30/src/main/java/org/apache/myfaces/test/util/JsfVersion.java b/test30/src/main/java/org/apache/myfaces/test/util/JsfVersion.java
new file mode 100644
index 0000000..82fee9f
--- /dev/null
+++ b/test30/src/main/java/org/apache/myfaces/test/util/JsfVersion.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.myfaces.test.util;
+
+import javax.faces.application.Application;
+
+public class JsfVersion
+{
+
+    private static boolean supports12;
+    private static boolean supports20;
+
+    static
+    {
+        try
+        {
+            Application.class.getMethod("getExpressionFactory");
+            supports12 = true;
+
+            try
+            {
+                Application.class.getMethod("getExceptionHandler");
+                supports20 = true;
+
+            }
+            catch (NoSuchMethodException e)
+            {
+                // ignore
+            }
+
+        }
+        catch (NoSuchMethodException e)
+        {
+            // ignore
+        }
+    }
+
+    private JsfVersion()
+    {
+        // avoid instantiation
+    }
+
+    /**
+     * Does the JSF is version 1.2 or higher
+     *
+     * @return Supports 1.2 or higher
+     */
+    public static boolean supports12()
+    {
+        return supports12;
+    }
+
+    /**
+     * Does the JSF is version 2.0 or higher
+     *
+     * @return Supports 2.0 or higher
+     */
+    public static boolean supports20()
+    {
+        return supports20;
+    }
+}