You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2018/10/21 08:14:24 UTC
[myfaces] branch master updated: Merged myfaces-test30 in core
This is an automated email from the ASF dual-hosted git repository.
tandraschko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces.git
The following commit(s) were added to refs/heads/master by this push:
new 0126d50 Merged myfaces-test30 in core
0126d50 is described below
commit 0126d50452e2b1a884fa490c7ff98d2dd7dcb501
Author: Thomas Andraschko <ta...@apache.org>
AuthorDate: Sun Oct 21 10:14:09 2018 +0200
Merged myfaces-test30 in core
---
impl/pom.xml | 6 +-
parent/pom.xml | 7 -
pom.xml | 1 +
test/pom.xml | 189 ++++
.../myfaces/test/base/AbstractJsfTestCase.java | 357 ++++++
.../test/base/AbstractViewControllerTestCase.java | 101 ++
.../AbstractJsfConfigurableMockTestCase.java | 359 ++++++
...actJsfConfigurableMultipleRequestsTestCase.java | 399 +++++++
.../test/base/junit4/AbstractJsfTestCase.java | 360 ++++++
.../junit4/AbstractViewControllerTestCase.java | 103 ++
.../apache/myfaces/test/cargo/CargoTestSetup.java | 179 +++
.../apache/myfaces/test/config/ConfigParser.java | 709 ++++++++++++
.../test/config/ResourceBundleVarNames.java | 50 +
.../apache/myfaces/test/el/AbstractELResolver.java | 78 ++
.../myfaces/test/el/ExpressionTokenizer.java | 193 ++++
.../test/el/FacesImplicitObjectELResolver.java | 317 ++++++
.../test/el/FacesResourceBundleELResolver.java | 236 ++++
.../test/el/FacesScopedAttributeELResolver.java | 269 +++++
.../test/el/FacesVariableResolverChainWrapper.java | 143 +++
.../test/el/MockCompositeValueExpression.java | 180 +++
.../org/apache/myfaces/test/el/MockELContext.java | 122 ++
.../myfaces/test/el/MockExpressionFactory.java | 365 ++++++
.../apache/myfaces/test/el/MockFunctionMapper.java | 74 ++
.../myfaces/test/el/MockMethodExpression.java | 267 +++++
.../myfaces/test/el/MockValueExpression.java | 307 +++++
.../apache/myfaces/test/el/MockVariableMapper.java | 80 ++
.../test/el/MockVariableValueExpression.java | 215 ++++
.../myfaces/test/el/ReservedWordsELResolver.java | 122 ++
.../test/htmlunit/AbstractHtmlUnitTestCase.java | 333 ++++++
.../htmlunit/junit4/AbstractHtmlUnitTestCase.java | 322 ++++++
.../test/jmock/AbstractJmockJsfTestCase.java | 354 ++++++
.../myfaces/test/mock/MockActionListener.java | 65 ++
.../apache/myfaces/test/mock/MockApplication.java | 407 +++++++
.../myfaces/test/mock/MockApplication12.java | 307 +++++
.../myfaces/test/mock/MockApplication20.java | 1188 ++++++++++++++++++++
.../myfaces/test/mock/MockApplication22.java | 62 +
.../myfaces/test/mock/MockApplication30.java | 62 +
.../myfaces/test/mock/MockApplicationFactory.java | 149 +++
.../apache/myfaces/test/mock/MockClientWindow.java | 104 ++
.../myfaces/test/mock/MockClientWindowFactory.java | 91 ++
.../apache/myfaces/test/mock/MockEnumeration.java | 77 ++
.../myfaces/test/mock/MockExceptionHandler.java | 220 ++++
.../test/mock/MockExceptionHandlerFactory.java | 40 +
.../myfaces/test/mock/MockExternalContext.java | 540 +++++++++
.../myfaces/test/mock/MockExternalContext12.java | 117 ++
.../myfaces/test/mock/MockExternalContext20.java | 358 ++++++
.../myfaces/test/mock/MockExternalContext22.java | 109 ++
.../apache/myfaces/test/mock/MockFacesContext.java | 330 ++++++
.../myfaces/test/mock/MockFacesContext12.java | 116 ++
.../myfaces/test/mock/MockFacesContext20.java | 183 +++
.../myfaces/test/mock/MockFacesContext22.java | 121 ++
.../myfaces/test/mock/MockFacesContextFactory.java | 315 ++++++
.../org/apache/myfaces/test/mock/MockFlash.java | 823 ++++++++++++++
.../apache/myfaces/test/mock/MockFlowHandler.java | 90 ++
.../myfaces/test/mock/MockHttpServletRequest.java | 1094 ++++++++++++++++++
.../myfaces/test/mock/MockHttpServletResponse.java | 474 ++++++++
.../apache/myfaces/test/mock/MockHttpSession.java | 454 ++++++++
.../myfaces/test/mock/MockHttpSessionProxy.java | 205 ++++
.../myfaces/test/mock/MockNavigationHandler.java | 110 ++
.../myfaces/test/mock/MockPartialViewContext.java | 375 ++++++
.../test/mock/MockPartialViewContextFactory.java | 41 +
.../myfaces/test/mock/MockPortletContext.java | 358 ++++++
.../myfaces/test/mock/MockPortletRequest.java | 436 +++++++
.../myfaces/test/mock/MockPortletResponse.java | 67 ++
.../myfaces/test/mock/MockPortletSession.java | 291 +++++
.../apache/myfaces/test/mock/MockPrincipal.java | 79 ++
.../apache/myfaces/test/mock/MockPrintWriter.java | 84 ++
.../apache/myfaces/test/mock/MockRenderKit.java | 145 +++
.../myfaces/test/mock/MockRenderKitFactory.java | 117 ++
.../myfaces/test/mock/MockRequestDispatcher.java | 60 +
.../test/mock/MockResponseStateManager.java | 396 +++++++
.../myfaces/test/mock/MockResponseWriter.java | 428 +++++++
.../org/apache/myfaces/test/mock/MockServlet.java | 119 ++
.../myfaces/test/mock/MockServletConfig.java | 124 ++
.../myfaces/test/mock/MockServletContext.java | 746 ++++++++++++
.../myfaces/test/mock/MockServletInputStream.java | 54 +
.../myfaces/test/mock/MockServletOutputStream.java | 93 ++
.../myfaces/test/mock/MockServletRegistration.java | 103 ++
.../apache/myfaces/test/mock/MockStateManager.java | 112 ++
.../apache/myfaces/test/mock/MockSubKeyMap.java | 315 ++++++
.../apache/myfaces/test/mock/MockViewHandler.java | 169 +++
.../myfaces/test/mock/MockViewHandler20.java | 52 +
.../apache/myfaces/test/mock/MockWebContainer.java | 299 +++++
.../myfaces/test/mock/MockedJsfTestContainer.java | 415 +++++++
.../myfaces/test/mock/_AbstractAttributeMap.java | 448 ++++++++
.../apache/myfaces/test/mock/_ApplicationMap.java | 78 ++
.../org/apache/myfaces/test/mock/_CookieMap.java | 167 +++
.../java/org/apache/myfaces/test/mock/_ELText.java | 444 ++++++++
.../java/org/apache/myfaces/test/mock/_Hex.java | 341 ++++++
.../myfaces/test/mock/_InitParameterMap.java | 80 ++
.../apache/myfaces/test/mock/_NullEnumeration.java | 49 +
.../myfaces/test/mock/_RequestHeaderMap.java | 80 ++
.../myfaces/test/mock/_RequestHeaderValuesMap.java | 93 ++
.../org/apache/myfaces/test/mock/_RequestMap.java | 78 ++
.../myfaces/test/mock/_RequestParameterMap.java | 67 ++
.../test/mock/_RequestParameterValuesMap.java | 67 ++
.../org/apache/myfaces/test/mock/_SessionMap.java | 88 ++
.../mock/lifecycle/ApplyRequestValuesExecutor.java | 45 +
.../mock/lifecycle/DefaultRestoreViewSupport.java | 124 ++
.../mock/lifecycle/InvokeApplicationExecutor.java | 45 +
.../myfaces/test/mock/lifecycle/MockLifecycle.java | 201 ++++
.../test/mock/lifecycle/MockLifecycleFactory.java | 87 ++
.../myfaces/test/mock/lifecycle/PhaseExecutor.java | 49 +
.../test/mock/lifecycle/PhaseListenerManager.java | 126 +++
.../mock/lifecycle/ProcessValidationsExecutor.java | 43 +
.../mock/lifecycle/RenderResponseExecutor.java | 60 +
.../test/mock/lifecycle/RestoreViewExecutor.java | 199 ++++
.../test/mock/lifecycle/RestoreViewSupport.java | 74 ++
.../mock/lifecycle/UpdateModelValuesExecutor.java | 45 +
.../resource/MockClassLoaderResourceLoader.java | 130 +++
.../MockExternalContextResourceLoader.java | 223 ++++
.../myfaces/test/mock/resource/MockResource.java | 105 ++
.../test/mock/resource/MockResourceHandler.java | 401 +++++++
.../mock/resource/MockResourceHandlerSupport.java | 124 ++
.../test/mock/resource/MockResourceLoader.java | 158 +++
.../test/mock/resource/MockResourceMeta.java | 123 ++
.../test/mock/resource/MockSimpleResource.java | 200 ++++
.../mock/resource/MockSimpleResourceHandler.java | 410 +++++++
.../myfaces/test/mock/visit/FullVisitContext.java | 148 +++
.../myfaces/test/mock/visit/MockVisitCallback.java | 41 +
.../myfaces/test/mock/visit/MockVisitContext.java | 106 ++
.../test/mock/visit/MockVisitContextFactory.java | 53 +
.../test/mock/visit/PartialVisitContext.java | 469 ++++++++
.../apache/myfaces/test/runners/NamedRunner.java | 126 +++
.../myfaces/test/runners/TestClassLoader.java | 359 ++++++
.../test/runners/TestPerClassLoaderRunner.java | 299 +++++
.../org/apache/myfaces/test/util/Jsf11Utils.java | 30 +
.../org/apache/myfaces/test/util/Jsf12Utils.java | 39 +
.../org/apache/myfaces/test/util/JsfVersion.java | 78 ++
129 files changed, 27277 insertions(+), 9 deletions(-)
diff --git a/impl/pom.xml b/impl/pom.xml
index 5bef8ce..46c0554 100644
--- a/impl/pom.xml
+++ b/impl/pom.xml
@@ -1187,8 +1187,10 @@
</dependency>
<dependency>
- <groupId>org.apache.myfaces.test</groupId>
- <artifactId>myfaces-test30</artifactId>
+ <groupId>org.apache.myfaces.core</groupId>
+ <artifactId>myfaces-test</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
</dependency>
<!-- easymock -->
diff --git a/parent/pom.xml b/parent/pom.xml
index 8038a17..4964872 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -557,13 +557,6 @@
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.apache.myfaces.test</groupId>
- <artifactId>myfaces-test30</artifactId>
- <version>1.0.9-SNAPSHOT</version>
- <scope>test</scope>
- </dependency>
-
<!-- easymock -->
<dependency>
<groupId>org.easymock</groupId>
diff --git a/pom.xml b/pom.xml
index 5903851..733777b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,7 @@
<modules>
<module>parent</module>
<module>api</module>
+ <module>test</module>
<module>impl</module>
<module>bundle</module>
</modules>
diff --git a/test/pom.xml b/test/pom.xml
new file mode 100644
index 0000000..5a6d6ba
--- /dev/null
+++ b/test/pom.xml
@@ -0,0 +1,189 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $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.core</groupId>
+ <artifactId>myfaces-core-project</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.apache.myfaces.core</groupId>
+ <artifactId>myfaces-test</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache MyFaces Core 3.0 - Test</name>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>net.sourceforge.htmlunit</groupId>
+ <artifactId>htmlunit</artifactId>
+ <version>1.14</version>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-digester</groupId>
+ <artifactId>commons-digester</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>jmock</groupId>
+ <artifactId>jmock</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>jmock</groupId>
+ <artifactId>jmock-cglib</artifactId>
+ <scope>compile</scope>
+ </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>${project.version}</version>
+ <scope>provided</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-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>
+</project>
diff --git a/test/src/main/java/org/apache/myfaces/test/base/AbstractJsfTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/AbstractJsfTestCase.java
new file mode 100644
index 0000000..4f76c28
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/base/AbstractViewControllerTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/AbstractViewControllerTestCase.java
new file mode 100644
index 0000000..aefc876
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMockTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMockTestCase.java
new file mode 100644
index 0000000..7f65067
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMultipleRequestsTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfConfigurableMultipleRequestsTestCase.java
new file mode 100644
index 0000000..6e52e3c
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractJsfTestCase.java
new file mode 100644
index 0000000..78f6694
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractViewControllerTestCase.java b/test/src/main/java/org/apache/myfaces/test/base/junit4/AbstractViewControllerTestCase.java
new file mode 100644
index 0000000..2f2e7c2
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/cargo/CargoTestSetup.java b/test/src/main/java/org/apache/myfaces/test/cargo/CargoTestSetup.java
new file mode 100644
index 0000000..103fe4f
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/config/ConfigParser.java b/test/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
new file mode 100644
index 0000000..b0ce0d6
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/config/ResourceBundleVarNames.java b/test/src/main/java/org/apache/myfaces/test/config/ResourceBundleVarNames.java
new file mode 100644
index 0000000..2edc340
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/AbstractELResolver.java b/test/src/main/java/org/apache/myfaces/test/el/AbstractELResolver.java
new file mode 100644
index 0000000..6ec43c2
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/ExpressionTokenizer.java b/test/src/main/java/org/apache/myfaces/test/el/ExpressionTokenizer.java
new file mode 100644
index 0000000..6798cec
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/FacesImplicitObjectELResolver.java b/test/src/main/java/org/apache/myfaces/test/el/FacesImplicitObjectELResolver.java
new file mode 100644
index 0000000..94e7155
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/FacesResourceBundleELResolver.java b/test/src/main/java/org/apache/myfaces/test/el/FacesResourceBundleELResolver.java
new file mode 100644
index 0000000..e7adb25
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/FacesScopedAttributeELResolver.java b/test/src/main/java/org/apache/myfaces/test/el/FacesScopedAttributeELResolver.java
new file mode 100644
index 0000000..bd2e6df
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/FacesVariableResolverChainWrapper.java b/test/src/main/java/org/apache/myfaces/test/el/FacesVariableResolverChainWrapper.java
new file mode 100644
index 0000000..e3b89de
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockCompositeValueExpression.java b/test/src/main/java/org/apache/myfaces/test/el/MockCompositeValueExpression.java
new file mode 100644
index 0000000..ce77bab
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockELContext.java b/test/src/main/java/org/apache/myfaces/test/el/MockELContext.java
new file mode 100644
index 0000000..2b2130f
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockExpressionFactory.java b/test/src/main/java/org/apache/myfaces/test/el/MockExpressionFactory.java
new file mode 100644
index 0000000..34e5c74
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockFunctionMapper.java b/test/src/main/java/org/apache/myfaces/test/el/MockFunctionMapper.java
new file mode 100644
index 0000000..4d7d15d
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockMethodExpression.java b/test/src/main/java/org/apache/myfaces/test/el/MockMethodExpression.java
new file mode 100644
index 0000000..04b065a
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockValueExpression.java b/test/src/main/java/org/apache/myfaces/test/el/MockValueExpression.java
new file mode 100644
index 0000000..8489ea7
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockVariableMapper.java b/test/src/main/java/org/apache/myfaces/test/el/MockVariableMapper.java
new file mode 100644
index 0000000..b19e7bd
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/MockVariableValueExpression.java b/test/src/main/java/org/apache/myfaces/test/el/MockVariableValueExpression.java
new file mode 100644
index 0000000..734d911
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/el/ReservedWordsELResolver.java b/test/src/main/java/org/apache/myfaces/test/el/ReservedWordsELResolver.java
new file mode 100644
index 0000000..2c048b5
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/htmlunit/AbstractHtmlUnitTestCase.java b/test/src/main/java/org/apache/myfaces/test/htmlunit/AbstractHtmlUnitTestCase.java
new file mode 100644
index 0000000..b1e62b0
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/htmlunit/junit4/AbstractHtmlUnitTestCase.java b/test/src/main/java/org/apache/myfaces/test/htmlunit/junit4/AbstractHtmlUnitTestCase.java
new file mode 100644
index 0000000..648b1de
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/jmock/AbstractJmockJsfTestCase.java b/test/src/main/java/org/apache/myfaces/test/jmock/AbstractJmockJsfTestCase.java
new file mode 100644
index 0000000..2bd5f95
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockActionListener.java b/test/src/main/java/org/apache/myfaces/test/mock/MockActionListener.java
new file mode 100644
index 0000000..3aa9937
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplication.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplication.java
new file mode 100644
index 0000000..e32f2a8
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplication12.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplication12.java
new file mode 100644
index 0000000..d13da73
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
new file mode 100644
index 0000000..ad24f1a
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplication22.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplication22.java
new file mode 100644
index 0000000..68dc315
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplication30.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplication30.java
new file mode 100644
index 0000000..382c8f0
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockApplicationFactory.java b/test/src/main/java/org/apache/myfaces/test/mock/MockApplicationFactory.java
new file mode 100644
index 0000000..9a76fdd
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockClientWindow.java b/test/src/main/java/org/apache/myfaces/test/mock/MockClientWindow.java
new file mode 100644
index 0000000..4c2b539
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockClientWindowFactory.java b/test/src/main/java/org/apache/myfaces/test/mock/MockClientWindowFactory.java
new file mode 100644
index 0000000..6e57e32
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockEnumeration.java b/test/src/main/java/org/apache/myfaces/test/mock/MockEnumeration.java
new file mode 100644
index 0000000..ea452e8
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java b/test/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java
new file mode 100644
index 0000000..2e7a0c0
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java b/test/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java
new file mode 100644
index 0000000..8072d1f
--- /dev/null
+++ b/test/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/test/src/main/java/org/apache/myfaces/test/mock/MockExternalContext.java b/test/src/main/java/org/apache/myfaces/test/mock/MockExternalContext.java
new file mode 100644
index 0000000..1774fa0
--- /dev/null
+++ b/test/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
+ *
... 18230 lines suppressed ...