You are viewing a plain text version of this content. The canonical link for it is here.
Posted to pluto-scm@portals.apache.org by as...@apache.org on 2019/04/09 21:00:08 UTC

[portals-pluto] branch master updated: PLUTO-772 Implement MVCBean portlets

This is an automated email from the ASF dual-hosted git repository.

asfgriff pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/portals-pluto.git


The following commit(s) were added to refs/heads/master by this push:
     new 03b0ef0  PLUTO-772 Implement MVCBean portlets
03b0ef0 is described below

commit 03b0ef0982d6473962cc17b6f3775af1b922810a
Author: Neil Griffin <ne...@gmail.com>
AuthorDate: Sat Apr 6 16:02:46 2019 -0400

    PLUTO-772 Implement MVCBean portlets
---
 README                                             |  23 +
 .../apache/pluto/maven/InstallationDependency.java | 114 +++--
 .../src/main/resources/versions.properties         |  19 +-
 .../pluto/container/driver/PortletServlet3.java    |  17 +-
 pluto-container/pom.xml                            |  28 ++
 .../bean/mvc/AfterControllerEventImpl.java         |  32 ++
 .../bean/mvc/AfterProcessViewEventImpl.java        |  31 ++
 .../bean/mvc/BaseControllerEventImpl.java          |  43 ++
 .../bean/mvc/BaseProcessViewEventImpl.java         |  42 ++
 .../apache/pluto/container/bean/mvc/BeanUtil.java  |  51 +++
 .../bean/mvc/BeanValidationInterceptor.java        | 155 +++++++
 .../bean/mvc/BeanValidationInterceptorBinding.java |  36 ++
 .../mvc/BeanValidationMessageInterpolator.java     |  34 ++
 .../container/bean/mvc/BeanValidationProducer.java | 105 +++++
 .../bean/mvc/BeanValidationValidator.java          |  34 ++
 .../bean/mvc/BeforeControllerEventImpl.java        |  32 ++
 .../bean/mvc/BeforeProcessViewEventImpl.java       |  31 ++
 .../pluto/container/bean/mvc/BindingErrorImpl.java |  38 ++
 .../container/bean/mvc/BindingResultImpl.java      | 123 +++++
 .../container/bean/mvc/ConfigurationImpl.java      | 132 ++++++
 .../container/bean/mvc/ControllerInterceptor.java  | 295 ++++++++++++
 .../bean/mvc/ControllerInterceptorBinding.java     |  36 ++
 .../bean/mvc/ControllerRedirectEventImpl.java      |  42 ++
 .../apache/pluto/container/bean/mvc/CsrfImpl.java  |  46 ++
 .../bean/mvc/CsrfValidationInterceptor.java        | 134 ++++++
 .../bean/mvc/CsrfValidationInterceptorBinding.java |  36 ++
 .../bean/mvc/DescendingPriorityComparator.java     |  61 +++
 .../pluto/container/bean/mvc/EncodersImpl.java     |  38 ++
 .../container/bean/mvc/LocaleResolverChain.java    |  51 +++
 .../bean/mvc/LocaleResolverContextImpl.java        |  79 ++++
 .../container/bean/mvc/LocaleResolverImpl.java     |  50 ++
 .../bean/mvc/MessageInterpolatorContextImpl.java   |  49 ++
 .../pluto/container/bean/mvc/ModelsImpl.java       |  74 +++
 .../container/bean/mvc/ModifiedAnnotatedType.java  |  90 ++++
 .../pluto/container/bean/mvc/ModifiedField.java    |  88 ++++
 .../pluto/container/bean/mvc/ModifiedMethod.java   | 103 +++++
 .../container/bean/mvc/MutableBindingResult.java   |  35 ++
 .../pluto/container/bean/mvc/MvcContextImpl.java   | 146 ++++++
 .../pluto/container/bean/mvc/MvcExtension.java     | 220 +++++++++
 .../container/bean/mvc/MvcRuntimeDelegate.java     |  72 +++
 .../bean/mvc/ParamConverterProviderImpl.java       | 184 ++++++++
 .../bean/mvc/ParamConverterProviderManager.java    |  64 +++
 .../bean/mvc/ParamConverterProviders.java          |  34 ++
 .../bean/mvc/ParamConverterProvidersProducer.java  |  57 +++
 .../pluto/container/bean/mvc/ParamErrorImpl.java   |  44 ++
 .../pluto/container/bean/mvc/PortletParam.java     |  34 ++
 .../container/bean/mvc/PortletParamProducer.java   | 507 +++++++++++++++++++++
 .../pluto/container/bean/mvc/RequestImpl.java      |  62 +++
 .../pluto/container/bean/mvc/ResourceInfoImpl.java |  46 ++
 .../container/bean/mvc/ServletContextProducer.java |  63 +++
 .../pluto/container/bean/mvc/SpringCsrfToken.java  |  66 +++
 .../pluto/container/bean/mvc/UriInfoImpl.java      | 127 ++++++
 .../container/bean/mvc/ValidationErrorImpl.java    |  39 ++
 .../container/bean/mvc/ViewEngineContextImpl.java  | 145 ++++++
 .../bean/mvc/ViewEngineContextProducer.java        |  39 ++
 .../container/bean/mvc/ViewEngineJspImpl.java      |  71 +++
 .../pluto/container/bean/mvc/ViewEngines.java      |  34 ++
 .../container/bean/mvc/ViewEnginesProducer.java    |  51 +++
 .../pluto/container/bean/mvc/ViewRenderer.java     |  38 ++
 .../container/bean/mvc/ViewRendererMvcImpl.java    | 126 +++++
 .../bean/processor/AnnotatedConfigBean.java        |  15 +
 .../bean/processor/MethodAnnotationRecognizer.java |   6 +-
 .../bean/processor/MethodDescription.java          |  20 +-
 .../processor/PortletAnnotationRecognizer.java     | 125 ++++-
 .../bean/processor/PortletCDIExtension.java        |   6 +
 .../container/bean/processor/PortletInvoker.java   |  45 +-
 .../processor/PortletSessionScopedBeanMap.java     |  19 +
 .../bean/processor/RedirectScopedBeanHolder.java   | 176 +++++++
 .../bean/processor/RedirectScopedConfig.java       | 146 ++++++
 .../bean/processor/RedirectScopedContext.java      |  87 ++++
 .../container/bean/processor/SignatureVariant.java |  34 +-
 .../om/portlet/impl/ConfigurationHolder.java       |  15 +-
 .../services/javax.ws.rs.ext.RuntimeDelegate       |   1 +
 .../processor/fixtures/RedirectScopedClass.java    |  35 ++
 .../bean/processor/tests/RedirectScopedTest.java   |  96 ++++
 .../container/PortletAsyncContextImpl.java         |   4 +
 pom.xml                                            | 102 +++--
 portlet-tck_3.0/V2AddlRequestTests/pom.xml         |   1 -
 portlet-tck_3.0/V3HeaderPortletTests/pom.xml       |   1 -
 79 files changed, 5467 insertions(+), 163 deletions(-)

diff --git a/README b/README
index 67cc429..2b76575 100644
--- a/README
+++ b/README
@@ -63,3 +63,26 @@ http://localhost:8080/pluto/portal
 
 Use the credentials pluto/pluto to log on to the application.
 
+Third-Party Licensing
+=====================
+
+The Apache Pluto software is compiled against version 2.1.1 of the JAX-RS API
+which is made available under the Eclipse Public License, version 2.0 (EPL 2.0).
+
+In addition, the software is compiled against version 1.3.1 of the Common
+Annotations for the Java(TM) Platform API (Commons-Annotations API) which is
+made available under the Common Development and Distribution License (CDDL).
+
+According to the [ASF 3RD PARTY LICENSE POLICY]
+(https://apache.org/legal/resolved.html#weak-copyleft-licenses), third-party
+software that is licensed under the EPL 2.0 or CDDL licenses may be included
+in binary form. To that end, the binary version of the JAX-RS API dependency
+is found in the following path of the Pluto+Tomcat bundle:
+
+    <tomcat-home>/lib/javax.ws.rs-api-2.1.1.jar
+
+Apache Tomcat includes its own version of the Commons-Annotations API (which is
+licensed under the Apache License, Version 2.0) in the following-path of the
+Pluto-Tomcat bundle:
+
+    <tomcat-home>/lib/annotations-api.jar
diff --git a/maven-pluto-plugin/src/main/java/org/apache/pluto/maven/InstallationDependency.java b/maven-pluto-plugin/src/main/java/org/apache/pluto/maven/InstallationDependency.java
index 93f23a6..84e80da 100644
--- a/maven-pluto-plugin/src/main/java/org/apache/pluto/maven/InstallationDependency.java
+++ b/maven-pluto-plugin/src/main/java/org/apache/pluto/maven/InstallationDependency.java
@@ -23,10 +23,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
 
-//import sun.security.provider.SHA;
-
-//import com.sun.corba.se.impl.protocol.SharedCDRClientRequestDispatcherImpl;
-
 /**
  *
  */
@@ -47,15 +43,47 @@ class InstallationDependency {
     }
 
     public static final InstallationDependency PORTLET_API =
-        new InstallationDependency("javax.portlet", "portlet-api", 
-                VERSION_PROPERTIES.getProperty("portals.portlet-api.version"));
+        new InstallationDependency("javax.portlet", "portlet-api",
+            VERSION_PROPERTIES.getProperty("portals.portlet-api.version"));
+
+    public static final InstallationDependency COMMONS_LANG3 =
+        new InstallationDependency("org.apache.commons", "commons-lang3",
+            VERSION_PROPERTIES.getProperty("commons-lang.version"));
+
+    public static final InstallationDependency CLASSMATE =
+        new InstallationDependency("com.fasterxml", "classmate",
+            VERSION_PROPERTIES.getProperty("classmate.version"));
+
+    public static final InstallationDependency HIBERNATE_VALIDATOR =
+        new InstallationDependency("org.hibernate.validator", "hibernate-validator",
+            VERSION_PROPERTIES.getProperty("hibernate-validator.version"));
+
+    public static final InstallationDependency HIBERNATE_VALIDATOR_CDI =
+        new InstallationDependency("org.hibernate.validator", "hibernate-validator-cdi",
+            VERSION_PROPERTIES.getProperty("hibernate-validator.version"));
+
+    public static final InstallationDependency JAXRS_API =
+        new InstallationDependency("javax.ws.rs", "javax.ws.rs-api",
+            VERSION_PROPERTIES.getProperty("jaxrs-api.version"));
+
+    public static final InstallationDependency JBOSS_LOGGING =
+        new InstallationDependency("org.jboss.logging", "jboss-logging",
+            VERSION_PROPERTIES.getProperty("jboss-logging.version"));
+
+    public static final InstallationDependency MVC_API =
+        new InstallationDependency("javax.mvc", "javax.mvc-api",
+            VERSION_PROPERTIES.getProperty("mvc-api.version"));
+
+    public static final InstallationDependency VALIDATION_API =
+        new InstallationDependency("javax.validation", "validation-api",
+            VERSION_PROPERTIES.getProperty("validation-api.version"));
 
     public static final InstallationDependency CDI_IMPL =
-        new InstallationDependency("org.jboss.weld.servlet", "weld-servlet", 
+        new InstallationDependency("org.jboss.weld.servlet", "weld-servlet",
                 VERSION_PROPERTIES.getProperty("cdi.version"));
 
     public static final InstallationDependency ANNOTATION_DETECTOR =
-        new InstallationDependency("eu.infomas", "annotation-detector", 
+        new InstallationDependency("eu.infomas", "annotation-detector",
                 VERSION_PROPERTIES.getProperty("annotation-detector.version"));
 
     public static final InstallationDependency SLF4J_API =
@@ -67,10 +95,10 @@ class InstallationDependency {
                 VERSION_PROPERTIES.getProperty("slf4j.version"));
 
     public static final InstallationDependency CONTAINER_API =
-        new InstallationDependency("org.apache.portals.pluto", "pluto-container-api", 
+        new InstallationDependency("org.apache.portals.pluto", "pluto-container-api",
                 VERSION_PROPERTIES.getProperty("pluto.version"));
 
-    public static final InstallationDependency  CONTAINER =
+    public static final InstallationDependency CONTAINER =
             new InstallationDependency("org.apache.portals.pluto", "pluto-container", 
                     VERSION_PROPERTIES.getProperty("pluto.version"));
 
@@ -79,75 +107,35 @@ class InstallationDependency {
                     VERSION_PROPERTIES.getProperty("pluto.version"));
 
     public static final InstallationDependency CONTAINER_DRIVER_API =
-        new InstallationDependency("org.apache.portals.pluto", "pluto-container-driver-api", 
+        new InstallationDependency("org.apache.portals.pluto", "pluto-container-driver-api",
                 VERSION_PROPERTIES.getProperty("pluto.version"));
 
-    public static final InstallationDependency  PORTAL =
-            new InstallationDependency("org.apache.portals.pluto", "pluto-portal", 
+    public static final InstallationDependency PORTAL =
+            new InstallationDependency("org.apache.portals.pluto", "pluto-portal",
                     VERSION_PROPERTIES.getProperty("pluto.version"), "war");
 
-    public static final InstallationDependency  TESTSUITE =
+    public static final InstallationDependency TESTSUITE =
             new InstallationDependency("org.apache.portals.pluto", "pluto-testsuite", 
                     VERSION_PROPERTIES.getProperty("pluto.version"), "war");
 
-    public static final InstallationDependency  CASTOR =
-            new InstallationDependency("org.codehaus.castor", "castor", 
-                    VERSION_PROPERTIES.getProperty("castor.version"));
-
-    public static final InstallationDependency  COMMONS_LOGGING_API =
-            new InstallationDependency("commons-logging", "commons-logging-api",
-                    VERSION_PROPERTIES.getProperty("commons-logging.version"));
-
-    public static final InstallationDependency  XERCES =
-            new InstallationDependency("xerces", "xercesImpl", 
-                    VERSION_PROPERTIES.getProperty("xercesImpl.version"));
-
-    public static final InstallationDependency  XML_PARSER_APIS =
-            new InstallationDependency("xerces", "xmlParserAPIs", 
-                    VERSION_PROPERTIES.getProperty("xmlParserAPIs.version"));
-    
-    
-    public static final InstallationDependency JAXB_API =
-		new InstallationDependency("javax.xml.bind","jaxb-api",
-				VERSION_PROPERTIES.getProperty("jaxb.version"));
-    public static final InstallationDependency JAXB_IMPL =
-		new InstallationDependency("com.sun.xml.bind","jaxb-impl",
-				VERSION_PROPERTIES.getProperty("jaxb-impl.version"));
-    
-    public static final InstallationDependency STAX_API =
-		new InstallationDependency("stax","stax-api",
-				VERSION_PROPERTIES.getProperty("stax.api.version"));
-    public static final InstallationDependency STAX_IMPL =
-		new InstallationDependency("stax","stax",
-				VERSION_PROPERTIES.getProperty("stax.impl.version"));
-    
-    public static final InstallationDependency XALAN =
-		new InstallationDependency("xalan","xalan",
-				VERSION_PROPERTIES.getProperty("xalan.version"));
-    public static final InstallationDependency ACTIVATION =
-		new InstallationDependency("javax.activation","activation",
-				VERSION_PROPERTIES.getProperty("activation.version"));
-
     public static final InstallationDependency CCPP_API =
 		new InstallationDependency("javax.ccpp","ccpp",
 				VERSION_PROPERTIES.getProperty("ccpp-api.version"));
-//    public static final InstallationDependency CCPP_IMPL=
-//		new InstallationDependency("com.sun.ccpp","ccpp-ri",
-//				VERSION_PROPERTIES.getProperty("ccpp-ri.version"));
-//    public static final InstallationDependency RDF_FILTER =
-//		new InstallationDependency("com.megginson.sax.rdf.rdffilter","rdffilter",
-//				VERSION_PROPERTIES.getProperty("rdffilter.version"));
-//    public static final InstallationDependency JENA =
-//		new InstallationDependency("com.hp.hpl.jena","jena",
-//				VERSION_PROPERTIES.getProperty("jena.version"));
- 
-    
+
 
     private static final List<InstallationDependency> ENDORSED = new ArrayList<InstallationDependency>();
     private static final List<InstallationDependency> SHARED = new ArrayList<InstallationDependency>();
 
     static {
         SHARED.add(PORTLET_API);
+        SHARED.add(COMMONS_LANG3);
+        SHARED.add(CLASSMATE);
+        SHARED.add(HIBERNATE_VALIDATOR);
+        SHARED.add(HIBERNATE_VALIDATOR_CDI);
+        SHARED.add(JAXRS_API);
+        SHARED.add(JBOSS_LOGGING);
+        SHARED.add(MVC_API);
+        SHARED.add(VALIDATION_API);
         SHARED.add(CONTAINER);
         SHARED.add(CONTAINER_API);
         SHARED.add(CONTAINER_DRIVER_API);
diff --git a/maven-pluto-plugin/src/main/resources/versions.properties b/maven-pluto-plugin/src/main/resources/versions.properties
index f9a9083..2a64c3a 100644
--- a/maven-pluto-plugin/src/main/resources/versions.properties
+++ b/maven-pluto-plugin/src/main/resources/versions.properties
@@ -28,16 +28,9 @@ annotation-detector.version=${annotation-detector.version}
 slf4j.version=${slf4j.version}
 jstl.version=${jstl.version}
 taglibs.standard.version=${taglibs.standard.version}
-xerces.version=${xerces.version}
-xercesImpl.version=${xerces.version}
-xmlParserAPIs.version=${xerces.version}
-castor.version=${castor.version}
 commons-digester.version=${commons-digester.version}
 commons-cli.version=${commons-cli.version}
-commons-httpclient.version=${commons-httpclient.version}
-commons-beanutils.version=${commons-beanutils.version}
 commons-logging.version=${commons-logging.version}
-log4j.version=${log4j.version}
 springframework.version=${springframework.version}
 plexus.version=${plexus.version}
 maven.version=${maven.version}
@@ -49,11 +42,13 @@ pluto.version=${pom.version}
 #pluto.version=${pom.parent.version}
 jaxb.version=${jaxb.version}
 jaxb-impl.version=${jaxb-impl.version}
-xalan.version=${xalan.version}
-activation.version=${activation.version}
 stax.api.version=${stax.api.version}
 stax.impl.version=${stax.impl.version}
 ccpp-api.version=${ccpp-api.version}
-ccpp-ri.version=${ccpp-ri.version}
-rdffilter.version=${rdffilter.version}
-jena.version=${jena.version}
+commons-lang.version=${commons-lang.version}
+classmate.version=${classmate.version}
+jaxrs-api.version=${jaxrs-api.version}
+jboss-logging.version=${jboss-logging.version}
+hibernate-validator.version=${hibernate-validator.version}
+mvc-api.version=${mvc-api.version}
+validation-api.version=${validation-api.version}
diff --git a/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PortletServlet3.java b/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PortletServlet3.java
index aeeea83..3da4fdf 100644
--- a/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PortletServlet3.java
+++ b/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PortletServlet3.java
@@ -70,6 +70,7 @@ import org.apache.pluto.container.bean.processor.PortletInvoker;
 import org.apache.pluto.container.bean.processor.PortletRequestScopedBeanHolder;
 import org.apache.pluto.container.bean.processor.PortletSessionBeanHolder;
 import org.apache.pluto.container.bean.processor.PortletStateScopedBeanHolder;
+import org.apache.pluto.container.bean.processor.RedirectScopedBeanHolder;
 import org.apache.pluto.container.impl.HttpServletPortletRequestWrapper;
 import org.apache.pluto.container.om.portlet.impl.ConfigurationHolder;
 import org.slf4j.Logger;
@@ -187,6 +188,7 @@ public class PortletServlet3 extends HttpServlet {
             Bean<?> bean = beanmgr.resolve(beans);
             acb = (AnnotatedConfigBean) beanmgr.getReference(bean, bean.getBeanClass(), beanmgr.createCreationalContext(bean));
             LOG.debug("ACB instance: " + acb + ", RS config: " + ((acb==null) ? "null" : acb.getSessionScopedConfig()));
+            acb.getRedirectScopedConfig().activate(beanmgr);
             acb.getSessionScopedConfig().activate(beanmgr);
             acb.getStateScopedConfig().activate(beanmgr);
          } catch (Throwable t) {
@@ -580,7 +582,7 @@ public class PortletServlet3 extends HttpServlet {
             request.removeAttribute(PortletInvokerService.PORTLET_RESPONSE);
             request.removeAttribute(PortletInvokerService.FILTER_MANAGER);
 
-            afterInvoke(portletResponse);
+            afterInvoke(portletRequest, portletResponse);
 
          } else {
             LOG.debug("Async started, not releasing resources. executing req body: " + requestContext.isExecutingRequestBody());
@@ -630,6 +632,9 @@ public class PortletServlet3 extends HttpServlet {
          // Set the portlet session bean holder for the thread & session
          PortletSessionBeanHolder.setBeanHolder(req, acb.getSessionScopedConfig());
 
+         // Set the redirect scoped bean holder
+         RedirectScopedBeanHolder.setBeanHolder(req);
+
          // Set the render state scoped bean holder
          PortletStateScopedBeanHolder.setBeanHolder(req, acb.getStateScopedConfig());
 
@@ -652,7 +657,7 @@ public class PortletServlet3 extends HttpServlet {
     * must be called after all method invocations have taken place, even if an
     * exception occurs.
     */
-   private void afterInvoke(PortletResponse resp) {
+   private void afterInvoke(PortletRequest portletRequest, PortletResponse portletResponse) {
 
       if (acb != null) {
 
@@ -662,13 +667,17 @@ public class PortletServlet3 extends HttpServlet {
          // Remove the portlet session bean holder for the thread
          PortletSessionBeanHolder.removeBeanHolder();
 
+         // Remove the redirect bean holder for the thread
+         RedirectScopedBeanHolder.removeBeanHolder(
+             (portletRequest instanceof RenderRequest) && !(portletRequest instanceof HeaderRequest));
+
          // Remove the render state bean holder. pass response if we're
          // dealing with a StateAwareResponse. The response is used for state
          // storage.
 
          StateAwareResponse sar = null;
-         if (resp instanceof StateAwareResponse) {
-            sar = (StateAwareResponse) resp;
+         if (portletResponse instanceof StateAwareResponse) {
+            sar = (StateAwareResponse) portletResponse;
          }
          PortletStateScopedBeanHolder.removeBeanHolder(sar);
 
diff --git a/pluto-container/pom.xml b/pluto-container/pom.xml
index aedaecd..d7e28df 100644
--- a/pluto-container/pom.xml
+++ b/pluto-container/pom.xml
@@ -34,6 +34,10 @@
 
       <!-- Other Support Libraries =========================================== -->
       <dependency>
+         <groupId>org.apache.commons</groupId>
+         <artifactId>commons-lang3</artifactId>
+      </dependency>
+      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <scope>provided</scope>
@@ -47,6 +51,12 @@
 
       <!-- Specification Libraries =========================================== -->
       <dependency>
+         <groupId>javax.annotation</groupId>
+         <artifactId>javax.annotation-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
          <groupId>javax.portlet</groupId>
          <artifactId>portlet-api</artifactId>
          <version>${project.version}</version>
@@ -54,6 +64,24 @@
       </dependency>
 
       <dependency>
+         <groupId>javax.mvc</groupId>
+         <artifactId>javax.mvc-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.validation</groupId>
+         <artifactId>validation-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.ws.rs</groupId>
+         <artifactId>javax.ws.rs-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
          <groupId>org.apache.tomcat</groupId>
          <artifactId>tomcat-servlet-api</artifactId>
          <scope>provided</scope>
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterControllerEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterControllerEventImpl.java
new file mode 100644
index 0000000..a7ab68d
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterControllerEventImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.event.AfterControllerEvent;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class AfterControllerEventImpl extends BaseControllerEventImpl implements AfterControllerEvent {
+
+	public AfterControllerEventImpl(ResourceInfo resourceInfo, UriInfo uriInfo) {
+		super(resourceInfo, uriInfo);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterProcessViewEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterProcessViewEventImpl.java
new file mode 100644
index 0000000..c1bdc1d
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/AfterProcessViewEventImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.engine.ViewEngine;
+import javax.mvc.event.AfterProcessViewEvent;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class AfterProcessViewEventImpl extends BaseProcessViewEventImpl implements AfterProcessViewEvent {
+
+	public AfterProcessViewEventImpl(String view, Class<? extends ViewEngine> viewEngine) {
+		super(view, viewEngine);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseControllerEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseControllerEventImpl.java
new file mode 100644
index 0000000..ee77c1a
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseControllerEventImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class BaseControllerEventImpl {
+
+	private ResourceInfo resourceInfo;
+	private UriInfo uriInfo;
+
+	public BaseControllerEventImpl(ResourceInfo resourceInfo, UriInfo uriInfo) {
+		this.resourceInfo = resourceInfo;
+		this.uriInfo = uriInfo;
+	}
+
+	public ResourceInfo getResourceInfo() {
+		return resourceInfo;
+	}
+
+	public UriInfo getUriInfo() {
+		return uriInfo;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseProcessViewEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseProcessViewEventImpl.java
new file mode 100644
index 0000000..25defb2
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BaseProcessViewEventImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.engine.ViewEngine;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class BaseProcessViewEventImpl {
+
+	private Class<? extends ViewEngine> viewEngine;
+	private String view;
+
+	public BaseProcessViewEventImpl(String view, Class<? extends ViewEngine> viewEngine) {
+		this.view = view;
+		this.viewEngine = viewEngine;
+	}
+
+	public Class<? extends ViewEngine> getEngine() {
+		return viewEngine;
+	}
+
+	public String getView() {
+		return view;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanUtil.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanUtil.java
new file mode 100644
index 0000000..8fc3fda
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanUtil.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Vetoed
+public class BeanUtil {
+
+	public static <T> List<T> getBeanInstances(BeanManager beanManager, Class<T> type, Annotation... qualifiers) {
+
+		List<T> beanInstances = new ArrayList<>();
+
+		if (qualifiers == null) {
+			qualifiers = new Annotation[] {};
+		}
+
+		Set<Bean<?>> beans = beanManager.getBeans(type, qualifiers);
+
+		for (Bean<?> bean : beans) {
+			beanInstances.add((T) beanManager.getReference(bean, type, beanManager.createCreationalContext(bean)));
+		}
+
+		return beanInstances;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptor.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptor.java
new file mode 100644
index 0000000..1b1ac3f
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptor.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.mvc.MvcContext;
+import javax.validation.ConstraintViolation;
+import javax.validation.ElementKind;
+import javax.validation.MessageInterpolator;
+import javax.validation.Path;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.QueryParam;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Interceptor
+@BeanValidationInterceptorBinding
+@Priority(Interceptor.Priority.LIBRARY_BEFORE)
+public class BeanValidationInterceptor implements Serializable {
+
+	private static final long serialVersionUID = 2378576156374329311L;
+
+	@BeanValidationMessageInterpolator
+	@Inject
+	private MessageInterpolator messageInterpolator;
+
+	@Inject
+	private MutableBindingResult mutableBindingResult;
+
+	@Inject
+	private MvcContext mvcContext;
+
+	@Inject
+	private ValidatorFactory validatorFactory;
+
+	@AroundInvoke
+	public Object validateMethodInvocation(InvocationContext invocationContext) throws Exception {
+
+		Validator validator = validatorFactory.getValidator();
+
+		if (validator == null) {
+			return invocationContext.proceed();
+		}
+
+		Set<ConstraintViolation<Object>> constraintViolations = validator.validate(invocationContext.getTarget());
+
+		for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
+
+			Path propertyPath = constraintViolation.getPropertyPath();
+
+			Path.Node lastPathNode = null;
+
+			for (Path.Node pathNode : propertyPath) {
+				lastPathNode = pathNode;
+			}
+
+			if ((lastPathNode != null) && (lastPathNode.getKind() == ElementKind.PROPERTY)) {
+
+				Object leafBean = constraintViolation.getLeafBean();
+				Class<?> leafBeanClass = leafBean.getClass();
+
+				BeanInfo beanInfo = Introspector.getBeanInfo(leafBean.getClass());
+
+				PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+
+				for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
+					String propertyDescriptorName = propertyDescriptor.getName();
+
+					if (propertyDescriptorName.equals("targetClass")) {
+						Method readMethod = propertyDescriptor.getReadMethod();
+						leafBeanClass = (Class<?>) readMethod.invoke(leafBean);
+					}
+				}
+
+				Field leafBeanField = leafBeanClass.getDeclaredField(lastPathNode.getName());
+
+				String paramName = null;
+
+				Annotation[] annotations = leafBeanField.getAnnotations();
+
+				for (Annotation annotation : annotations) {
+
+					if (annotation instanceof CookieParam) {
+						CookieParam cookieParam = (CookieParam) annotation;
+						paramName = cookieParam.value();
+
+						break;
+					}
+					else if (annotation instanceof FormParam) {
+						FormParam formParam = (FormParam) annotation;
+						paramName = formParam.value();
+
+						break;
+					}
+					else if (annotation instanceof HeaderParam) {
+						HeaderParam headerParam = (HeaderParam) annotation;
+						paramName = headerParam.value();
+
+						break;
+					}
+					else if (annotation instanceof QueryParam) {
+						QueryParam queryParam = (QueryParam) annotation;
+						paramName = queryParam.value();
+					}
+				}
+
+				String interpolatedMessage = constraintViolation.getMessage();
+
+				if (messageInterpolator != null) {
+					interpolatedMessage = messageInterpolator.interpolate(constraintViolation.getMessageTemplate(),
+							new MessageInterpolatorContextImpl(constraintViolation), mvcContext.getLocale());
+				}
+
+				mutableBindingResult.addValidationError(new ValidationErrorImpl(paramName, interpolatedMessage,
+						constraintViolation));
+			}
+
+		}
+
+		return invocationContext.proceed();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptorBinding.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptorBinding.java
new file mode 100644
index 0000000..eeea311
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationInterceptorBinding.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.interceptor.InterceptorBinding;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Inherited
+@InterceptorBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface BeanValidationInterceptorBinding {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationMessageInterpolator.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationMessageInterpolator.java
new file mode 100644
index 0000000..a800e89
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationMessageInterpolator.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+public @interface BeanValidationMessageInterpolator {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationProducer.java
new file mode 100644
index 0000000..82726b8
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationProducer.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Iterator;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.validation.MessageInterpolator;
+import javax.validation.NoProviderFoundException;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class BeanValidationProducer {
+
+	private static final Logger LOG = LoggerFactory.getLogger(BeanValidationProducer.class);
+
+	private MessageInterpolator messageInterpolator;
+	private Validator validator;
+
+	@Inject
+	private Instance<ValidatorFactory> validatorFactoryInstance;
+
+	@BeanValidationMessageInterpolator
+	@Dependent
+	@Produces
+	public MessageInterpolator getMessageInterpolator() {
+		return messageInterpolator;
+	}
+
+	@BeanValidationValidator
+	@Dependent
+	@Produces
+	public Validator getValidator() {
+		return validator;
+	}
+
+	@PostConstruct
+	public void postConstruct() {
+		ValidatorFactory validatorFactory = null;
+
+		Iterator<ValidatorFactory> iterator = validatorFactoryInstance.iterator();
+
+		if (iterator.hasNext()) {
+			validatorFactory = iterator.next();
+		}
+
+		if (validatorFactory == null) {
+
+			if (LOG.isWarnEnabled()) {
+				LOG.warn("ValidatorFactory was not injected -- if using Hibernate " + "Validator, please include the " +
+					"hibernate-validator-cdi dependency.");
+			}
+
+			try {
+				validatorFactory = Validation.buildDefaultValidatorFactory();
+			}
+			catch (NoProviderFoundException npfe) {
+				LOG.error(npfe.getMessage(), npfe);
+			}
+		}
+
+		if (validatorFactory != null) {
+			messageInterpolator = validatorFactory.getMessageInterpolator();
+
+			if ((messageInterpolator == null) && LOG.isWarnEnabled()) {
+				LOG.warn("Bean validation MessageInterpolator not available");
+			}
+
+			validator = validatorFactory.getValidator();
+
+			if ((validator == null) && LOG.isWarnEnabled()) {
+				LOG.warn("Bean validation validator not available");
+			}
+		}
+	}
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationValidator.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationValidator.java
new file mode 100644
index 0000000..78e8ada
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeanValidationValidator.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+public @interface BeanValidationValidator {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeControllerEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeControllerEventImpl.java
new file mode 100644
index 0000000..7c261f4
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeControllerEventImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.event.BeforeControllerEvent;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class BeforeControllerEventImpl extends BaseControllerEventImpl implements BeforeControllerEvent {
+
+	public BeforeControllerEventImpl(ResourceInfo resourceInfo, UriInfo uriInfo) {
+		super(resourceInfo, uriInfo);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeProcessViewEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeProcessViewEventImpl.java
new file mode 100644
index 0000000..cfa6ccc
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BeforeProcessViewEventImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.engine.ViewEngine;
+import javax.mvc.event.BeforeProcessViewEvent;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class BeforeProcessViewEventImpl extends BaseProcessViewEventImpl implements BeforeProcessViewEvent {
+
+	public BeforeProcessViewEventImpl(String view, Class<? extends ViewEngine> viewEngine) {
+		super(view, viewEngine);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingErrorImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingErrorImpl.java
new file mode 100644
index 0000000..6163ce3
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingErrorImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.binding.BindingError;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class BindingErrorImpl extends ParamErrorImpl implements BindingError {
+
+	private String submittedValue;
+
+	public BindingErrorImpl(String paramName, String message, String submittedValue) {
+		super(paramName, message);
+		this.submittedValue = submittedValue;
+	}
+
+	@Override
+	public String getSubmittedValue() {
+		return submittedValue;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingResultImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingResultImpl.java
new file mode 100644
index 0000000..582956c
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/BindingResultImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.mvc.RedirectScoped;
+import javax.mvc.binding.BindingError;
+import javax.mvc.binding.ParamError;
+import javax.mvc.binding.ValidationError;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@RedirectScoped
+public class BindingResultImpl implements MutableBindingResult, Serializable {
+
+	private static final long serialVersionUID = 2217732469752342741L;
+
+	private Set<BindingError> bindingErrors = new LinkedHashSet<>();
+	private boolean consulted = false;
+	private Set<ValidationError> validationErrors = new LinkedHashSet<>();
+
+	@Override
+	public void addBindingError(BindingError bindingError) {
+		bindingErrors.add(bindingError);
+	}
+
+	@Override
+	public void addValidationError(ValidationError validationError) {
+		validationErrors.add(validationError);
+	}
+
+	public void clear() {
+		bindingErrors.clear();
+		validationErrors.clear();
+		consulted = false;
+	}
+
+	@Override
+	public Set<ParamError> getAllErrors() {
+
+		consulted = true;
+
+		Set<ParamError> allErrors = new LinkedHashSet<>();
+		allErrors.addAll(bindingErrors);
+		allErrors.addAll(validationErrors);
+
+		return allErrors;
+	}
+
+	@Override
+	public List<String> getAllMessages() {
+
+		consulted = true;
+
+		List<String> allMessages = new ArrayList<>();
+
+		for (BindingError bindingError : bindingErrors) {
+			allMessages.add(bindingError.getMessage());
+		}
+
+		for (ValidationError validationError : validationErrors) {
+			allMessages.add(validationError.getMessage());
+		}
+
+		return allMessages;
+	}
+
+	@Override
+	public Set<ParamError> getErrors(String paramName) {
+		consulted = true;
+
+		Set<ParamError> errors = new LinkedHashSet<>();
+
+		for (BindingError bindingError : bindingErrors) {
+
+			if (Objects.equals(bindingError.getParamName(), paramName)) {
+				errors.add(bindingError);
+			}
+		}
+
+		for (ValidationError validationError : validationErrors) {
+
+			if (Objects.equals(validationError.getParamName(), paramName)) {
+				errors.add(validationError);
+			}
+		}
+
+		return errors;
+	}
+
+	public boolean isConsulted() {
+		return consulted;
+	}
+
+	@Override
+	public boolean isFailed() {
+		consulted = true;
+
+		return !bindingErrors.isEmpty() || !validationErrors.isEmpty();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ConfigurationImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ConfigurationImpl.java
new file mode 100644
index 0000000..e8b0e31
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ConfigurationImpl.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ConfigurationImpl implements Configuration {
+
+	public static final String DEFAULT_VIEW_EXTENSION = "org.apache.pluto.defaultViewExtension";
+
+	private Map<String, Object> properties;
+
+	@Inject
+	private PortletConfig portletConfig;
+
+	@Inject
+	private PortletContext portletContext;
+
+	@Override
+	public Set<Class<?>> getClasses() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Map<Class<?>, Integer> getContracts(Class<?> componentClass) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Set<Object> getInstances() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Map<String, Object> getProperties() {
+
+		if (properties == null) {
+
+			properties = new HashMap<>();
+
+			Enumeration<String> initParameterNames = portletConfig.getInitParameterNames();
+
+			while (initParameterNames.hasMoreElements()) {
+				String initParameterName = initParameterNames.nextElement();
+				properties.put(initParameterName, portletConfig.getInitParameter(initParameterName));
+			}
+
+			initParameterNames = portletContext.getInitParameterNames();
+
+			while (initParameterNames.hasMoreElements()) {
+				String initParameterName = initParameterNames.nextElement();
+				properties.put(initParameterName, portletContext.getInitParameter(initParameterName));
+			}
+
+			if (!properties.containsKey(DEFAULT_VIEW_EXTENSION)) {
+				properties.put(DEFAULT_VIEW_EXTENSION, "jsp");
+			}
+		}
+
+		return properties;
+	}
+
+	@Override
+	public Object getProperty(String name) {
+
+		Map<String, Object> properties = getProperties();
+
+		return properties.get(name);
+	}
+
+	@Override
+	public Collection<String> getPropertyNames() {
+
+		Map<String, Object> properties = getProperties();
+
+		return properties.keySet();
+	}
+
+	@Override
+	public RuntimeType getRuntimeType() {
+		return RuntimeType.SERVER;
+	}
+
+	@Override
+	public boolean isEnabled(Feature feature) {
+		return false;
+	}
+
+	@Override
+	public boolean isEnabled(Class<? extends Feature> featureClass) {
+		return false;
+	}
+
+	@Override
+	public boolean isRegistered(Object component) {
+		return false;
+	}
+
+	@Override
+	public boolean isRegistered(Class<?> componentClass) {
+		return false;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptor.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptor.java
new file mode 100644
index 0000000..cd2bebc
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptor.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.annotation.Priority;
+import javax.enterprise.event.Event;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.mvc.View;
+import javax.mvc.event.MvcEvent;
+import javax.portlet.ActionResponse;
+import javax.portlet.BaseURL;
+import javax.portlet.MimeResponse;
+import javax.portlet.MutableRenderParameters;
+import javax.portlet.MutableResourceParameters;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.ResourceResponse;
+import javax.portlet.annotations.ActionMethod;
+import javax.portlet.annotations.DestroyMethod;
+import javax.portlet.annotations.EventMethod;
+import javax.portlet.annotations.InitMethod;
+import javax.portlet.annotations.RenderMethod;
+import javax.portlet.filter.RenderURLWrapper;
+import javax.portlet.filter.ResourceURLWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+
+import org.apache.pluto.container.bean.processor.MethodType;
+import org.apache.pluto.container.bean.processor.PortletInvoker;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Interceptor
+@ControllerInterceptorBinding
+@Priority(Interceptor.Priority.LIBRARY_BEFORE)
+public class ControllerInterceptor implements Serializable {
+
+	private static final long serialVersionUID = 1573287230987622411L;
+
+	private static final Logger LOG = LoggerFactory.getLogger(ControllerInterceptor.class);
+
+	@Inject
+	private ActionResponse actionResponse;
+
+	@Inject
+	private Event<MvcEvent> mvcEvent;
+
+	@Inject
+	private MimeResponse mimeResponse;
+
+	@Inject
+	private PortletConfig portletConfig;
+
+	@Inject
+	private PortletRequest portletRequest;
+
+	@Inject
+	private ResourceResponse resourceResponse;
+
+	@Inject
+	private ViewRenderer viewRenderer;
+
+	@AroundInvoke
+	public Object processView(InvocationContext invocationContext) throws Exception {
+
+		PortletRequest portletRequest = this.portletRequest;
+		MimeResponse mimeResponse = this.mimeResponse;
+
+		// Set the viewName request attribute before invoking the controller
+		// @RenderMethod or @ServeResourceMethod. This makes it possible for
+		// the developer to determine the viewName that may have been set by an
+		// @ActionMethod in the ACTION_PHASE (or ResourceURL redirect) by
+		// calling the ViewEngineContext.getView() method.
+
+		String viewNameFromSession = null;
+
+		Method invocationContextMethod = invocationContext.getMethod();
+
+		MethodType methodType = _getMethodType(invocationContextMethod);
+
+		if ((methodType == MethodType.RENDER) || (methodType == MethodType.RESOURCE)) {
+
+			Object[] parameters = invocationContext.getParameters();
+
+			if ((parameters != null) && (parameters.length > 1)) {
+
+				portletRequest = (PortletRequest) parameters[0];
+				mimeResponse = (MimeResponse) parameters[1];
+			}
+
+			if (portletRequest.getParameter(ViewRenderer.REDIRECTED_VIEW) != null) {
+
+				PortletSession portletSession = portletRequest.getPortletSession(true);
+
+				viewNameFromSession = (String) portletSession.getAttribute(ViewRenderer.VIEW_NAME);
+
+				portletSession.removeAttribute(ViewRenderer.VIEW_NAME);
+
+				if (viewNameFromSession != null) {
+					portletRequest.setAttribute(ViewRenderer.VIEW_NAME, viewNameFromSession);
+				}
+			}
+		}
+
+		Object target = invocationContext.getTarget();
+
+		mvcEvent.fire(new BeforeControllerEventImpl(new ResourceInfoImpl(target.getClass(), invocationContextMethod),
+				new UriInfoImpl()));
+
+		Object result = invocationContext.proceed();
+
+		BaseURL redirectURL = null;
+
+		boolean renderView = true;
+
+		String viewName = null;
+
+		if (_isNull(result)) {
+			View view = invocationContextMethod.getAnnotation(View.class);
+
+			if (view != null) {
+				viewName = view.value();
+			}
+		}
+		else {
+			viewName = result.toString();
+		}
+
+		if (!_isNull(viewName) && ((methodType == MethodType.ACTION) || (methodType == MethodType.RESOURCE))) {
+
+			PortletSession portletSession = portletRequest.getPortletSession(true);
+
+			if (viewName.startsWith(ViewRenderer.REDIRECT_PREFIX)) {
+				viewName = viewName.substring(ViewRenderer.REDIRECT_PREFIX.length());
+
+				if (methodType == MethodType.ACTION) {
+					redirectURL = new ActionRedirectURL(actionResponse);
+				}
+				else {
+					redirectURL = new ResourceRedirectURL(resourceResponse);
+				}
+			}
+
+			portletSession.setAttribute(ViewRenderer.VIEW_NAME, viewName);
+
+			if (methodType == MethodType.ACTION) {
+
+				if (redirectURL == null) {
+					MutableRenderParameters mutableRenderParameters = actionResponse.getRenderParameters();
+
+					mutableRenderParameters.setValue(ViewRenderer.REDIRECTED_VIEW, Boolean.TRUE.toString());
+				}
+				else {
+
+					try {
+						actionResponse.sendRedirect(redirectURL.toString());
+					}
+					catch (IOException ioe) {
+						LOG.error(ioe.getMessage(), ioe);
+					}
+				}
+			}
+			else {
+
+				if (redirectURL != null) {
+					resourceResponse.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+					resourceResponse.addProperty(HttpHeaders.LOCATION, redirectURL.toString());
+
+					renderView = false;
+				}
+			}
+		}
+
+		if (renderView) {
+
+			if (_isNull(viewName)) {
+				viewName = viewNameFromSession;
+			}
+
+			if (!_isNull(viewName)) {
+				portletRequest.setAttribute(ViewRenderer.VIEW_NAME, viewName);
+			}
+		}
+
+		target = invocationContext.getTarget();
+
+		mvcEvent.fire(new AfterControllerEventImpl(new ResourceInfoImpl(target.getClass(), invocationContextMethod),
+				new UriInfoImpl()));
+
+		if (redirectURL != null) {
+
+			try {
+				URI location = new URI(redirectURL.toString());
+
+				mvcEvent.fire(new ControllerRedirectEventImpl(
+						new ResourceInfoImpl(target.getClass(), invocationContextMethod), new UriInfoImpl(), location));
+			}
+			catch (URISyntaxException urise) {
+				LOG.error(urise.getMessage(), urise);
+			}
+		}
+
+		if ((viewName != null) && (portletRequest.getAttribute(PortletInvoker.LAST_METHOD) != null)) {
+			portletRequest.removeAttribute(PortletInvoker.LAST_METHOD);
+			viewRenderer.render(portletRequest, mimeResponse, portletConfig);
+		}
+
+		return null;
+	}
+
+	private MethodType _getMethodType(Method invocationContextMethod) {
+
+		if (invocationContextMethod.isAnnotationPresent(ActionMethod.class)) {
+			return MethodType.ACTION;
+		}
+
+		if (invocationContextMethod.isAnnotationPresent(DestroyMethod.class)) {
+			return MethodType.DESTROY;
+		}
+
+		if (invocationContextMethod.isAnnotationPresent(EventMethod.class)) {
+			return MethodType.EVENT;
+		}
+
+		if (invocationContextMethod.isAnnotationPresent(InitMethod.class)) {
+			return MethodType.INIT;
+		}
+
+		if (invocationContextMethod.isAnnotationPresent(RenderMethod.class)) {
+			return MethodType.RENDER;
+		}
+
+		return MethodType.RESOURCE;
+	}
+
+	private boolean _isNull(Object viewName) {
+		return (viewName == null) || viewName.toString().trim().toLowerCase().equals("null");
+	}
+
+	private static class ActionRedirectURL extends RenderURLWrapper {
+
+		public ActionRedirectURL(ActionResponse actionResponse) {
+			super(actionResponse.createRedirectURL(MimeResponse.Copy.ALL));
+
+			MutableRenderParameters mutableRenderParameters = getRenderParameters();
+
+			mutableRenderParameters.setValue(ViewRenderer.REDIRECTED_VIEW, Boolean.TRUE.toString());
+		}
+
+	}
+
+	private static class ResourceRedirectURL extends ResourceURLWrapper {
+
+		public ResourceRedirectURL(ResourceResponse resourceResponse) {
+			super(resourceResponse.createResourceURL());
+
+			MutableResourceParameters mutableResourceParameters = getResourceParameters();
+
+			mutableResourceParameters.setValue(ViewRenderer.REDIRECTED_VIEW, Boolean.TRUE.toString());
+
+			setResourceID(null);
+		}
+
+	}
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptorBinding.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptorBinding.java
new file mode 100644
index 0000000..30a7723
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerInterceptorBinding.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.interceptor.InterceptorBinding;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Inherited
+@InterceptorBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface ControllerInterceptorBinding {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerRedirectEventImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerRedirectEventImpl.java
new file mode 100644
index 0000000..f9c1825
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ControllerRedirectEventImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.net.URI;
+
+import javax.mvc.event.ControllerRedirectEvent;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ControllerRedirectEventImpl extends BaseControllerEventImpl implements ControllerRedirectEvent {
+
+	private URI location;
+
+	public ControllerRedirectEventImpl(ResourceInfo resourceInfo, UriInfo uriInfo, URI location) {
+		super(resourceInfo, uriInfo);
+		this.location = location;
+	}
+
+	@Override
+	public URI getLocation() {
+		return location;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfImpl.java
new file mode 100644
index 0000000..9366f67
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.enterprise.inject.Vetoed;
+import javax.mvc.security.Csrf;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Vetoed
+public class CsrfImpl implements Csrf {
+
+	private String name;
+	private String token;
+
+	public CsrfImpl(String name, String token) {
+		this.name = name;
+		this.token = token;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public String getToken() {
+		return token;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptor.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptor.java
new file mode 100644
index 0000000..c9d5103
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptor.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.mvc.MvcContext;
+import javax.mvc.security.Csrf;
+import javax.mvc.security.CsrfProtected;
+import javax.portlet.ClientDataRequest;
+import javax.ws.rs.core.Configuration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Interceptor
+@CsrfValidationInterceptorBinding
+@Priority(Interceptor.Priority.LIBRARY_BEFORE)
+public class CsrfValidationInterceptor implements Serializable {
+
+	private static final long serialVersionUID = 1348567603498123441L;
+
+	private static final Logger LOG = LoggerFactory.getLogger(CsrfValidationInterceptor.class);
+
+	@Inject
+	private Configuration configuration;
+
+	@Inject
+	private MvcContext mvcContext;
+
+	@AroundInvoke
+	public Object validateMethodInvocation(InvocationContext invocationContext) throws Exception {
+
+		Csrf.CsrfOptions csrfOptions = Csrf.CsrfOptions.EXPLICIT;
+
+		Object csrfProtection = configuration.getProperty(Csrf.CSRF_PROTECTION);
+
+		if (csrfProtection != null) {
+
+			if (csrfProtection instanceof Csrf.CsrfOptions) {
+				csrfOptions = (Csrf.CsrfOptions) csrfProtection;
+			}
+			else {
+
+				try {
+					csrfOptions = Csrf.CsrfOptions.valueOf(csrfProtection.toString());
+				}
+				catch (IllegalArgumentException e) {
+					LOG.error(e.getMessage(), e);
+				}
+			}
+		}
+
+		if (csrfOptions == Csrf.CsrfOptions.OFF) {
+			return invocationContext.proceed();
+		}
+
+		Method targetMethod = invocationContext.getMethod();
+
+		if ((csrfOptions == Csrf.CsrfOptions.EXPLICIT) && !targetMethod.isAnnotationPresent(CsrfProtected.class)) {
+			return invocationContext.proceed();
+		}
+
+		boolean proceed = false;
+
+		Object[] parameters = invocationContext.getParameters();
+
+		if (parameters.length == 2) {
+
+			if (parameters[0] instanceof ClientDataRequest) {
+
+				ClientDataRequest clientDataRequest = (ClientDataRequest) parameters[0];
+
+				String method = clientDataRequest.getMethod();
+
+				if (method.equalsIgnoreCase("post")) {
+
+					Csrf csrf = mvcContext.getCsrf();
+
+					String csrfRequestParameterValue = clientDataRequest.getParameter(csrf.getName());
+
+					String csrfToken = csrf.getToken();
+
+					if ((csrfToken != null) && csrfToken.equalsIgnoreCase(csrfRequestParameterValue)) {
+						proceed = true;
+					}
+					else {
+						LOG.error("Invalid CSRF token");
+					}
+
+				}
+				else {
+					proceed = true;
+				}
+			}
+			else {
+				LOG.error("First parameter of method signature must be ActionRequest or ResourceRequest");
+			}
+		}
+		else {
+			LOG.error("Method signature must include ActionRequest,ActionResponse or ResourceRequest,ResourceResponse");
+		}
+
+		if (proceed) {
+			return invocationContext.proceed();
+		}
+
+		return null;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptorBinding.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptorBinding.java
new file mode 100644
index 0000000..0054264
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/CsrfValidationInterceptorBinding.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.interceptor.InterceptorBinding;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Inherited
+@InterceptorBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface CsrfValidationInterceptorBinding {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/DescendingPriorityComparator.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/DescendingPriorityComparator.java
new file mode 100644
index 0000000..959b7c6
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/DescendingPriorityComparator.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Comparator;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.Vetoed;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Vetoed
+public class DescendingPriorityComparator<T> implements Comparator<Object> {
+
+	private int _defaultPriority;
+
+	public DescendingPriorityComparator() {
+		this(0);
+	}
+
+	public DescendingPriorityComparator(int defaultPriority) {
+		_defaultPriority = defaultPriority;
+	}
+
+	@Override
+	public int compare(Object object1, Object object2) {
+		Class<?> class1 = object1.getClass();
+		Priority priority1 = class1.getAnnotation(Priority.class);
+		Class<?> class2 = object2.getClass();
+		Priority priority2 = class2.getAnnotation(Priority.class);
+
+		if ((priority1 == null) && (priority2 == null)) {
+			return 0;
+		}
+		else if (priority1 == null) {
+			return Integer.compare(priority2.value(), _defaultPriority);
+		}
+		else if (priority2 == null) {
+			return Integer.compare(_defaultPriority, priority1.value());
+		}
+		else {
+			return Integer.compare(priority1.value(), priority2.value());
+		}
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/EncodersImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/EncodersImpl.java
new file mode 100644
index 0000000..85d5885
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/EncodersImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.security.Encoders;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class EncodersImpl implements Encoders {
+
+	@Override
+	public String html(String markup) {
+		return StringEscapeUtils.escapeHtml4(markup);
+	}
+
+	@Override
+	public String js(String code) {
+		return StringEscapeUtils.escapeEcmaScript(code);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverChain.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverChain.java
new file mode 100644
index 0000000..8f6536b
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverChain.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.mvc.locale.LocaleResolver;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class LocaleResolverChain {
+
+	private static List<LocaleResolver> localeResolvers;
+
+	@Inject
+	private BeanManager _beanManager;
+
+	public List<LocaleResolver> getLocaleResolvers() {
+		return localeResolvers;
+	}
+
+	@PostConstruct
+	private void postConstruct() {
+
+		localeResolvers = BeanUtil.getBeanInstances(_beanManager, LocaleResolver.class);
+
+		Collections.sort(localeResolvers, new DescendingPriorityComparator(1000));
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverContextImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverContextImpl.java
new file mode 100644
index 0000000..e0ca250
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverContextImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.mvc.locale.LocaleResolverContext;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class LocaleResolverContextImpl implements LocaleResolverContext {
+
+	private List<Locale> acceptableLanguages;
+	private Configuration configuration;
+	private Map<String, Cookie> cookies;
+	private Map<String, String> headers;
+	private UriInfo uriInfo;
+
+	public LocaleResolverContextImpl(List<Locale> acceptableLanguages, Configuration configuration,
+		Map<String, Cookie> cookies, Map<String, String> headers, UriInfo uriInfo) {
+		this.acceptableLanguages = acceptableLanguages;
+		this.configuration = configuration;
+		this.cookies = cookies;
+		this.headers = headers;
+		this.uriInfo = uriInfo;
+	}
+
+	@Override
+	public List<Locale> getAcceptableLanguages() {
+		return acceptableLanguages;
+	}
+
+	@Override
+	public Configuration getConfiguration() {
+		return configuration;
+	}
+
+	@Override
+	public Cookie getCookie(String name) {
+		return cookies.get(name);
+	}
+
+	@Override
+	public String getHeaderString(String name) {
+		return headers.get(name);
+	}
+
+	@Override
+	public Request getRequest() {
+		return new RequestImpl();
+	}
+
+	@Override
+	public UriInfo getUriInfo() {
+		return uriInfo;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverImpl.java
new file mode 100644
index 0000000..81db78c
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/LocaleResolverImpl.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.pluto.container.bean.mvc;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.mvc.locale.LocaleResolver;
+import javax.mvc.locale.LocaleResolverContext;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+@Priority(0)
+public class LocaleResolverImpl implements LocaleResolver {
+
+	@Override
+	public Locale resolveLocale(LocaleResolverContext localeResolverContext) {
+
+		List<Locale> acceptableLanguages = localeResolverContext.getAcceptableLanguages();
+
+		for (Locale acceptableLanguage : acceptableLanguages) {
+
+			if (!Objects.equals(acceptableLanguage.getLanguage(), "*")) {
+				return acceptableLanguage;
+			}
+		}
+
+		return Locale.getDefault();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MessageInterpolatorContextImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MessageInterpolatorContextImpl.java
new file mode 100644
index 0000000..e6c5eac
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MessageInterpolatorContextImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.MessageInterpolator;
+import javax.validation.metadata.ConstraintDescriptor;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class MessageInterpolatorContextImpl implements MessageInterpolator.Context {
+
+	private ConstraintViolation<?> constraintViolation;
+
+	public MessageInterpolatorContextImpl(ConstraintViolation<?> constraintViolation) {
+		this.constraintViolation = constraintViolation;
+	}
+
+	@Override
+	public ConstraintDescriptor<?> getConstraintDescriptor() {
+		return constraintViolation.getConstraintDescriptor();
+	}
+
+	@Override
+	public Object getValidatedValue() {
+		return constraintViolation.getInvalidValue();
+	}
+
+	@Override
+	public <T> T unwrap(Class<T> aClass) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModelsImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModelsImpl.java
new file mode 100644
index 0000000..6b76587
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModelsImpl.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.pluto.container.bean.mvc;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Named;
+import javax.mvc.Models;
+import javax.mvc.RedirectScoped;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Named("models")
+@RedirectScoped
+public class ModelsImpl implements Models, Serializable {
+
+	private static final long serialVersionUID = 2433287856890024741L;
+
+	private final Map<String, Object> modelsMap = new LinkedHashMap<>();
+
+	@Override
+	public Map<String, Object> asMap() {
+		return Collections.unmodifiableMap(modelsMap);
+	}
+
+	public void clear() {
+		modelsMap.clear();
+	}
+
+	@Override
+	public Object get(String name) {
+		return get(name, Object.class);
+	}
+
+	@Override
+	public <T> T get(String name, Class<T> type) {
+		return type.cast(modelsMap.get(name));
+	}
+
+	@Override
+	public Iterator<String> iterator() {
+		Set<String> keySet = modelsMap.keySet();
+
+		return keySet.iterator();
+	}
+
+	@Override
+	public Models put(String name, Object model) {
+		modelsMap.put(name, model);
+
+		return this;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedAnnotatedType.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedAnnotatedType.java
new file mode 100644
index 0000000..cda0c53
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedAnnotatedType.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import javax.enterprise.inject.spi.AnnotatedConstructor;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedType;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ModifiedAnnotatedType<X> implements AnnotatedType<X> {
+
+	private final AnnotatedType<X> annotatedType;
+	private final Set<AnnotatedField<? super X>> fields;
+	private final Set<AnnotatedMethod<? super X>> methods;
+
+	public ModifiedAnnotatedType(AnnotatedType<X> annotatedType, Set<AnnotatedField<? super X>> fields,
+		Set<AnnotatedMethod<? super X>> methods) {
+		this.annotatedType = annotatedType;
+		this.fields = fields;
+		this.methods = methods;
+	}
+
+	@Override
+	public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+		return annotatedType.getAnnotation(annotationType);
+	}
+
+	@Override
+	public Set<Annotation> getAnnotations() {
+		return annotatedType.getAnnotations();
+	}
+
+	@Override
+	public Type getBaseType() {
+		return annotatedType.getBaseType();
+	}
+
+	@Override
+	public Set<AnnotatedConstructor<X>> getConstructors() {
+		return annotatedType.getConstructors();
+	}
+
+	@Override
+	public Set<AnnotatedField<? super X>> getFields() {
+		return fields;
+	}
+
+	@Override
+	public Class<X> getJavaClass() {
+		return annotatedType.getJavaClass();
+	}
+
+	@Override
+	public Set<AnnotatedMethod<? super X>> getMethods() {
+		return methods;
+	}
+
+	@Override
+	public Set<Type> getTypeClosure() {
+		return annotatedType.getTypeClosure();
+	}
+
+	@Override
+	public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+		return annotatedType.isAnnotationPresent(annotationType);
+	}
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedField.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedField.java
new file mode 100644
index 0000000..b4294ae
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedField.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedType;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ModifiedField<X> implements AnnotatedField<X> {
+
+	private AnnotatedField annotatedField;
+	private Set<Annotation> annotations;
+
+	public ModifiedField(AnnotatedField<X> annotatedField, Set<Annotation> annotations) {
+		this.annotatedField = annotatedField;
+		this.annotations = annotations;
+	}
+
+	@Override
+	public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+
+		for (Annotation annotation : annotations) {
+
+			if (annotationClass.isAssignableFrom(annotation.getClass())) {
+				return (T) annotation;
+			}
+		}
+
+		return null;
+	}
+
+	@Override
+	public Set<Annotation> getAnnotations() {
+		return annotations;
+	}
+
+	@Override
+	public Type getBaseType() {
+		return annotatedField.getBaseType();
+	}
+
+	@Override
+	public AnnotatedType<X> getDeclaringType() {
+		return annotatedField.getDeclaringType();
+	}
+
+	@Override
+	public Field getJavaMember() {
+		return annotatedField.getJavaMember();
+	}
+
+	@Override
+	public Set<Type> getTypeClosure() {
+		return annotatedField.getTypeClosure();
+	}
+
+	@Override
+	public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
+		return getAnnotation(annotationClass) != null;
+	}
+
+	@Override
+	public boolean isStatic() {
+		return annotatedField.isStatic();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedMethod.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedMethod.java
new file mode 100644
index 0000000..ab5913f
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ModifiedMethod.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.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedParameter;
+import javax.enterprise.inject.spi.AnnotatedType;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ModifiedMethod<X> implements AnnotatedMethod<X> {
+
+	private AnnotatedMethod annotatedMethod;
+	private Set<Annotation> annotations;
+
+	public ModifiedMethod(AnnotatedMethod<X> annotatedMethod, Set<Annotation> annotations) {
+		this.annotatedMethod = annotatedMethod;
+		this.annotations = annotations;
+	}
+
+	@Override
+	public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+
+		for (Annotation annotation : annotations) {
+			Class<? extends Annotation> curAnnotationType = annotation.annotationType();
+
+			if (curAnnotationType.equals(annotationType)) {
+				return annotationType.cast(annotation);
+			}
+		}
+
+		return null;
+	}
+
+	@Override
+	public Set<Annotation> getAnnotations() {
+		return annotations;
+	}
+
+	@Override
+	public Type getBaseType() {
+		return annotatedMethod.getBaseType();
+	}
+
+	@Override
+	public AnnotatedType<X> getDeclaringType() {
+		return annotatedMethod.getDeclaringType();
+	}
+
+	@Override
+	public Method getJavaMember() {
+		return annotatedMethod.getJavaMember();
+	}
+
+	@Override
+	public List<AnnotatedParameter<X>> getParameters() {
+		return annotatedMethod.getParameters();
+	}
+
+	@Override
+	public Set<Type> getTypeClosure() {
+		return annotatedMethod.getTypeClosure();
+	}
+
+	@Override
+	public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+
+		Annotation annotation = getAnnotation(annotationType);
+
+		if (annotation == null) {
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	public boolean isStatic() {
+		return annotatedMethod.isStatic();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MutableBindingResult.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MutableBindingResult.java
new file mode 100644
index 0000000..4adc6ef
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MutableBindingResult.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.binding.BindingError;
+import javax.mvc.binding.BindingResult;
+import javax.mvc.binding.ValidationError;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public interface MutableBindingResult extends BindingResult {
+
+	public void addBindingError(BindingError bindingError);
+
+	public void addValidationError(ValidationError validationError);
+
+	public boolean isConsulted();
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcContextImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcContextImpl.java
new file mode 100644
index 0000000..f1bdac4
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcContextImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.mvc.MvcContext;
+import javax.mvc.locale.LocaleResolver;
+import javax.mvc.security.Csrf;
+import javax.mvc.security.Encoders;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.annotations.PortletRequestScoped;
+import javax.servlet.http.Cookie;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.UriBuilder;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Named("mvc")
+@PortletRequestScoped
+public class MvcContextImpl implements MvcContext {
+
+	@Inject
+	private Configuration configuration;
+
+	private Csrf csrf;
+
+	@Inject
+	private Encoders encoders;
+
+	private Locale locale;
+
+	@Inject
+	private LocaleResolverChain localeResolverChain;
+
+	@Inject
+	private PortletContext portletContext;
+
+	@Inject
+	private PortletRequest portletRequest;
+
+	@Override
+	public String getBasePath() {
+		return portletContext.getContextPath();
+	}
+
+	@Override
+	public Configuration getConfig() {
+		return configuration;
+	}
+
+	@Override
+	public Csrf getCsrf() {
+		return csrf;
+	}
+
+	@Override
+	public Encoders getEncoders() {
+		return encoders;
+	}
+
+	@Override
+	public Locale getLocale() {
+		return locale;
+	}
+
+	@PostConstruct
+	public void postConstruct() {
+
+		SpringCsrfToken springCsrfToken = new SpringCsrfToken(portletRequest.getAttribute(
+					"org.springframework.security.web.csrf.CsrfToken"));
+
+		csrf = new CsrfImpl(springCsrfToken.getName(), springCsrfToken.getValue());
+
+		List<LocaleResolver> localeResolvers = localeResolverChain.getLocaleResolvers();
+
+		Map<String, javax.ws.rs.core.Cookie> cookieMap = new HashMap<>();
+
+		Cookie[] cookies = portletRequest.getCookies();
+
+		for (Cookie cookie : cookies) {
+			cookieMap.put(cookie.getName(), new javax.ws.rs.core.Cookie(cookie.getName(), cookie.getValue()));
+		}
+
+		Map<String, String> headerMap = new HashMap<>();
+
+		Enumeration<String> propertyNames = portletRequest.getPropertyNames();
+
+		while (propertyNames.hasMoreElements()) {
+			String header = propertyNames.nextElement();
+			headerMap.put(header, portletRequest.getProperty(header));
+		}
+
+		LocaleResolverContextImpl localeResolverContext = new LocaleResolverContextImpl(Collections.list(
+					portletRequest.getLocales()), configuration, cookieMap, headerMap, new UriInfoImpl());
+
+		for (LocaleResolver localeResolver : localeResolvers) {
+			locale = localeResolver.resolveLocale(localeResolverContext);
+
+			if (locale != null) {
+				break;
+			}
+		}
+	}
+
+	@Override
+	public URI uri(String identifier) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public URI uri(String identifier, Map<String, Object> params) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public UriBuilder uriBuilder(String s) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcExtension.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcExtension.java
new file mode 100644
index 0000000..f862778
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcExtension.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.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.inject.Inject;
+import javax.mvc.Controller;
+import javax.portlet.annotations.ActionMethod;
+import javax.portlet.annotations.RenderMethod;
+import javax.portlet.annotations.ServeResourceMethod;
+import javax.validation.executable.ExecutableType;
+import javax.validation.executable.ValidateOnExecution;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Vetoed
+public class MvcExtension implements Extension {
+
+	private static final Logger LOG = LoggerFactory.getLogger(MvcExtension.class);
+
+	private static final Annotation BEAN_VALIDATION_INTERCEPTOR_BINDING = new BeanValidationInterceptorBinding() {
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return BeanValidationInterceptorBinding.class;
+			}
+		};
+
+	private static final Annotation CONTROLLER_INTERCEPTOR_BINDING = new ControllerInterceptorBinding() {
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return ControllerInterceptorBinding.class;
+			}
+		};
+
+	private static final Annotation CSRF_VALIDATION_INTERCEPTOR_BINDING = new CsrfValidationInterceptorBinding() {
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return CsrfValidationInterceptorBinding.class;
+			}
+		};
+
+	private static final Annotation INJECT = new Inject() {
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return Inject.class;
+			}
+
+		};
+
+	private static final Annotation PORTLET_PARAM = new PortletParam() {
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return PortletParam.class;
+			}
+
+		};
+
+	private static final Class<? extends Annotation>[] UNSUPPORTED_ANNOTATION_CLASSES = new Class[] {
+			MatrixParam.class, PathParam.class
+		};
+
+	public static <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> processAnnotatedType) {
+
+		AnnotatedType<T> annotatedType = processAnnotatedType.getAnnotatedType();
+
+		boolean modifiedMethods = false;
+
+		Set<AnnotatedMethod<? super T>> annotatedMethods = new LinkedHashSet<>();
+
+		boolean typeLevelController = annotatedType.isAnnotationPresent(Controller.class);
+
+		for (AnnotatedMethod<? super T> annotatedMethod : annotatedType.getMethods()) {
+
+			ActionMethod actionMethod = annotatedMethod.getAnnotation(ActionMethod.class);
+
+			RenderMethod renderMethod = annotatedMethod.getAnnotation(RenderMethod.class);
+
+			ServeResourceMethod serveResourceMethod = annotatedMethod.getAnnotation(ServeResourceMethod.class);
+
+			if (((actionMethod != null) || (renderMethod != null) || (serveResourceMethod != null)) &&
+					(typeLevelController || annotatedMethod.isAnnotationPresent(Controller.class))) {
+
+				Set<Annotation> methodAnnotations = new LinkedHashSet<>(annotatedMethod.getAnnotations());
+
+				methodAnnotations.add(CONTROLLER_INTERCEPTOR_BINDING);
+
+				modifiedMethods = true;
+
+				if ((actionMethod != null) || (serveResourceMethod != null)) {
+					methodAnnotations.add(CSRF_VALIDATION_INTERCEPTOR_BINDING);
+				}
+
+				ValidateOnExecution validateOnExecution = annotatedMethod.getAnnotation(ValidateOnExecution.class);
+
+				boolean addBeanValidationInterceptor = true;
+
+				if (validateOnExecution != null) {
+					ExecutableType[] executableTypes = validateOnExecution.type();
+
+					for (ExecutableType executableType : executableTypes) {
+
+						if (executableType == ExecutableType.NONE) {
+							addBeanValidationInterceptor = false;
+
+							break;
+						}
+					}
+				}
+
+				if (addBeanValidationInterceptor) {
+					methodAnnotations.add(BEAN_VALIDATION_INTERCEPTOR_BINDING);
+				}
+
+				annotatedMethod = new ModifiedMethod<>(annotatedMethod, methodAnnotations);
+			}
+
+			annotatedMethods.add(annotatedMethod);
+		}
+
+		boolean modifiedFieldAnnotations = false;
+
+		Set<AnnotatedField<? super T>> annotatedFields = new LinkedHashSet<>();
+
+		for (AnnotatedField<? super T> annotatedField : annotatedType.getFields()) {
+
+			Set<Annotation> fieldAnnotations = new LinkedHashSet<>(annotatedField.getAnnotations());
+
+			for (Class<? extends Annotation> unsupportedAnnotationClass : UNSUPPORTED_ANNOTATION_CLASSES) {
+
+				Annotation unsupportedAnnotation = annotatedField.getAnnotation(unsupportedAnnotationClass);
+
+				if (unsupportedAnnotation != null) {
+
+					fieldAnnotations.remove(unsupportedAnnotation);
+
+					modifiedFieldAnnotations = true;
+
+					if (LOG.isWarnEnabled()) {
+						LOG.warn("Removed unsupported @" + unsupportedAnnotationClass.getSimpleName() +
+							" annotation from " + annotatedField);
+					}
+				}
+
+			}
+
+			BeanParam beanParam = annotatedField.getAnnotation(BeanParam.class);
+			CookieParam cookieParam = annotatedField.getAnnotation(CookieParam.class);
+			FormParam formParam = annotatedField.getAnnotation(FormParam.class);
+			HeaderParam headerParam = annotatedField.getAnnotation(HeaderParam.class);
+			QueryParam queryParam = annotatedField.getAnnotation(QueryParam.class);
+
+			if ((beanParam != null) || (cookieParam != null) || (formParam != null) || (headerParam != null) ||
+					(queryParam != null)) {
+
+				if (!annotatedField.isAnnotationPresent(Inject.class)) {
+					fieldAnnotations.add(INJECT);
+					modifiedFieldAnnotations = true;
+				}
+
+				if (beanParam == null) {
+					fieldAnnotations.add(PORTLET_PARAM);
+					modifiedFieldAnnotations = true;
+				}
+
+			}
+
+			if (modifiedFieldAnnotations) {
+				annotatedField = new ModifiedField(annotatedField, fieldAnnotations);
+			}
+
+			annotatedFields.add(annotatedField);
+		}
+
+		if (modifiedMethods || modifiedFieldAnnotations) {
+			processAnnotatedType.setAnnotatedType(new ModifiedAnnotatedType<>(annotatedType, annotatedFields,
+					annotatedMethods));
+		}
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcRuntimeDelegate.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcRuntimeDelegate.java
new file mode 100644
index 0000000..6242e8f
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/MvcRuntimeDelegate.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.Variant;
+import javax.ws.rs.ext.RuntimeDelegate;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class MvcRuntimeDelegate extends RuntimeDelegate {
+
+	@Override
+	public <T> T createEndpoint(Application application, Class<T> aClass) throws IllegalArgumentException,
+		UnsupportedOperationException {
+		return null;
+	}
+
+	@Override
+	public <T> HeaderDelegate<T> createHeaderDelegate(Class<T> aClass) throws IllegalArgumentException {
+		return new HeaderDelegate<T>() {
+				@Override
+				public T fromString(String s) {
+					return (T) s;
+				}
+
+				@Override
+				public String toString(T t) {
+					return t.toString();
+				}
+			};
+	}
+
+	@Override
+	public Link.Builder createLinkBuilder() {
+		return null;
+	}
+
+	@Override
+	public Response.ResponseBuilder createResponseBuilder() {
+		return null;
+	}
+
+	@Override
+	public UriBuilder createUriBuilder() {
+		return null;
+	}
+
+	@Override
+	public Variant.VariantListBuilder createVariantListBuilder() {
+		return null;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderImpl.java
new file mode 100644
index 0000000..2fb5bcf
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderImpl.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+import javax.mvc.MvcContext;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.ext.ParamConverter;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ParamConverterProviderImpl implements ParamConverterProvider {
+
+	@Inject
+	private MvcContext mvcContext;
+
+	@Override
+	public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
+
+		if (rawType == null) {
+			return null;
+		}
+
+		if (rawType.equals(Integer.class) || rawType.equals(Integer.TYPE) || rawType.equals(Long.class) ||
+				rawType.equals(Long.TYPE) || rawType.equals(Double.class) || rawType.equals(Double.TYPE) ||
+				rawType.equals(Float.class) || rawType.equals(Float.TYPE) || rawType.equals(Boolean.class) ||
+				rawType.equals(Boolean.TYPE)) {
+
+			return new ParamConverter<T>() {
+					@Override
+					public T fromString(String value) {
+
+						if (value == null) {
+							throw new IllegalArgumentException("Unable to convert a null parameter value");
+						}
+
+						if (rawType.equals(Integer.class) || rawType.equals(Integer.TYPE)) {
+							Function<Number, Integer> numberIntegerFunction = new Function<Number, Integer>() {
+									@Override
+									public Integer apply(Number number) {
+										return number.intValue();
+									}
+								};
+
+							return _getNumber(value, numberIntegerFunction);
+						}
+						else if (rawType.equals(Long.class) || rawType.equals(Long.TYPE)) {
+							Function<Number, Long> numberLongFunction = new Function<Number, Long>() {
+									@Override
+									public Long apply(Number number) {
+										return number.longValue();
+									}
+								};
+
+							return _getNumber(value, numberLongFunction);
+						}
+						else if (rawType.equals(Double.class) || rawType.equals(Double.TYPE)) {
+							Function<Number, Double> numberDoubleFunction = new Function<Number, Double>() {
+									@Override
+									public Double apply(Number number) {
+										return number.doubleValue();
+									}
+								};
+
+							return _getNumber(value, numberDoubleFunction);
+						}
+						else if (rawType.equals(Float.class) || rawType.equals(Float.TYPE)) {
+							Function<Number, Float> numberFloatFunction = new Function<Number, Float>() {
+									@Override
+									public Float apply(Number number) {
+										return number.floatValue();
+									}
+								};
+
+							return _getNumber(value, numberFloatFunction);
+						}
+						else if (rawType.equals(Boolean.class) || rawType.equals(Boolean.TYPE)) {
+
+							if (value == null) {
+								return (T) Boolean.FALSE;
+							}
+
+							value = value.trim();
+							value = value.toLowerCase();
+
+							if (Boolean.valueOf(value)) {
+								return (T) Boolean.TRUE;
+							}
+
+							return (T) Boolean.valueOf(value.equals("on"));
+						}
+
+						return null;
+					}
+
+					private T _getNumber(String value, Function getNumberFunction) {
+						NumberFormat numberFormat = NumberFormat.getInstance(mvcContext.getLocale());
+
+						try {
+							Number number = numberFormat.parse(value);
+
+							return (T) getNumberFunction.apply(number);
+						}
+						catch (ParseException pe) {
+							throw new IllegalArgumentException(pe);
+						}
+					}
+
+					@Override
+					public String toString(T value) {
+
+						if (value == null) {
+							return "";
+						}
+
+						return value.toString();
+					}
+				};
+		}
+		else {
+			return null;
+		}
+	}
+
+	private String _getParamName(Annotation[] annotations) {
+
+		for (Annotation annotation : annotations) {
+
+			Class<? extends Annotation> annotationClass = annotation.getClass();
+
+			if (CookieParam.class.isAssignableFrom(annotationClass)) {
+				CookieParam cookieParam = (CookieParam) annotation;
+
+				return cookieParam.value();
+			}
+
+			if (FormParam.class.isAssignableFrom(annotationClass)) {
+				FormParam formParam = (FormParam) annotation;
+
+				return formParam.value();
+			}
+
+			if (HeaderParam.class.isAssignableFrom(annotationClass)) {
+				HeaderParam headerParam = (HeaderParam) annotation;
+
+				return headerParam.value();
+			}
+
+			if (QueryParam.class.isAssignableFrom(annotationClass)) {
+				QueryParam queryParam = (QueryParam) annotation;
+
+				return queryParam.value();
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderManager.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderManager.java
new file mode 100644
index 0000000..f2a2931
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviderManager.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.ws.rs.ext.ParamConverter;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class ParamConverterProviderManager {
+
+	private static List<ParamConverterProvider> paramConverterProviders;
+
+	@Inject
+	private BeanManager _beanManager;
+
+	public <T> ParamConverter getParamConverter(Class<?> rawType, Type baseType, Annotation[] annotations) {
+
+		for (ParamConverterProvider paramConverterProvider : paramConverterProviders) {
+			ParamConverter<T> paramConverter = paramConverterProvider.getConverter((Class<T>) rawType, baseType,
+					annotations);
+
+			if (paramConverter != null) {
+				return paramConverter;
+			}
+		}
+
+		return null;
+	}
+
+	@PostConstruct
+	public void postConstruct() {
+
+		paramConverterProviders = BeanUtil.getBeanInstances(_beanManager, ParamConverterProvider.class);
+
+		Collections.sort(paramConverterProviders, new DescendingPriorityComparator());
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviders.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviders.java
new file mode 100644
index 0000000..5b8f50c
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProviders.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+public @interface ParamConverterProviders {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProvidersProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProvidersProducer.java
new file mode 100644
index 0000000..52dbb23
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamConverterProvidersProducer.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class ParamConverterProvidersProducer {
+
+	@Inject
+	private BeanManager beanManager;
+
+	private List<ParamConverterProvider> paramConverterProviders;
+
+	@ApplicationScoped
+	@ParamConverterProviders
+	@Produces
+	public List<ParamConverterProvider> getParamConverterProviders() {
+		return paramConverterProviders;
+	}
+
+	@PostConstruct
+	public void postConstruct() {
+		paramConverterProviders = BeanUtil.getBeanInstances(beanManager, ParamConverterProvider.class);
+
+		paramConverterProviders.add(new ParamConverterProviderImpl());
+
+		Collections.sort(paramConverterProviders, new DescendingPriorityComparator());
+	}
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamErrorImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamErrorImpl.java
new file mode 100644
index 0000000..db6b9b9
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ParamErrorImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.binding.ParamError;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ParamErrorImpl implements ParamError {
+
+	private final String message;
+	private final String paramName;
+
+	public ParamErrorImpl(String paramName, String message) {
+		this.paramName = paramName;
+		this.message = message;
+	}
+
+	@Override
+	public String getMessage() {
+		return message;
+	}
+
+	@Override
+	public String getParamName() {
+		return paramName;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParam.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParam.java
new file mode 100644
index 0000000..c83697a
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParam.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+public @interface PortletParam {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParamProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParamProducer.java
new file mode 100644
index 0000000..cfbe42f
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/PortletParamProducer.java
@@ -0,0 +1,507 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.Annotated;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.inject.Inject;
+import javax.portlet.ClientDataRequest;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.servlet.http.Cookie;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.ext.ParamConverter;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Dependent
+public class PortletParamProducer {
+
+	private static final Logger LOG = LoggerFactory.getLogger(PortletParamProducer.class);
+
+	@Inject
+	private MutableBindingResult mutableBindingResult;
+
+	@Inject
+	@ParamConverterProviders
+	private List<ParamConverterProvider> paramConverterProviders;
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Boolean getBooleanParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Boolean> paramConverter = _getParamConverter(Boolean.class, field.getBaseType(),
+				fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Boolean");
+		}
+
+		return null;
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Date getDateParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Date> paramConverter = _getParamConverter(Date.class, field.getBaseType(), fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Date");
+		}
+
+		return null;
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Double getDoubleParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Double> paramConverter = _getParamConverter(Double.class, field.getBaseType(), fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Double");
+		}
+
+		return null;
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Float getFloatParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Float> paramConverter = _getParamConverter(Float.class, field.getBaseType(), fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Float");
+		}
+
+		return null;
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Integer getIntegerParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Integer> paramConverter = _getParamConverter(Integer.class, field.getBaseType(),
+				fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Integer");
+		}
+
+		return null;
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public Long getLongParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		String value = getStringParam(clientDataRequest, portletResponse, injectionPoint);
+
+		if (value == null) {
+			return null;
+		}
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		Annotation[] fieldAnnotations = _getFieldAnnotations(field);
+
+		ParamConverter<Long> paramConverter = _getParamConverter(Long.class, field.getBaseType(), fieldAnnotations);
+
+		if (paramConverter != null) {
+
+			try {
+				return paramConverter.fromString(value);
+			}
+			catch (IllegalArgumentException iae) {
+				_addBindingError(fieldAnnotations, iae.getMessage(), value);
+
+				return null;
+			}
+		}
+
+		if (LOG.isWarnEnabled()) {
+			LOG.warn("Unable to find a ParamConverterProvider for type Long");
+		}
+
+		return Long.valueOf(value);
+	}
+
+	@Dependent
+	@PortletParam
+	@Produces
+	public String getStringParam(ClientDataRequest clientDataRequest, PortletResponse portletResponse,
+		InjectionPoint injectionPoint) {
+
+		Annotated field = injectionPoint.getAnnotated();
+
+		String defaultValue = null;
+
+		DefaultValue defaultValueAnnotation = field.getAnnotation(DefaultValue.class);
+
+		if (defaultValueAnnotation != null) {
+			defaultValue = defaultValueAnnotation.value();
+		}
+
+		CookieParam cookieParam = field.getAnnotation(CookieParam.class);
+
+		if (cookieParam != null) {
+			Cookie[] cookies = clientDataRequest.getCookies();
+
+			for (Cookie cookie : cookies) {
+
+				if (Objects.equals(cookieParam.value(), cookie.getName())) {
+					String cookieValue = cookie.getValue();
+
+					if (cookieValue == null) {
+
+						if (LOG.isDebugEnabled()) {
+							LOG.debug("Injecting the default value=" + defaultValue + " into a field annotated " +
+								"with @CookieParam(\"" + cookieParam.value() +
+								"\") because the cookie does not have a " + "value.");
+						}
+
+						return defaultValue;
+					}
+
+					return cookieValue;
+				}
+			}
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("Injecting the default value=" + defaultValue +
+					" into a field annotated with @CookieParam(\"" + cookieParam.value() +
+					"\") because there is no cookie " + "with that name.");
+			}
+
+			return defaultValue;
+		}
+
+		FormParam formParam = field.getAnnotation(FormParam.class);
+
+		if (formParam != null) {
+			String lifecyclePhase = (String) clientDataRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE);
+
+			String httpMethod = clientDataRequest.getMethod();
+
+			if ("post".equalsIgnoreCase(httpMethod) &&
+					(lifecyclePhase.equals(PortletRequest.ACTION_PHASE) ||
+						lifecyclePhase.equals(PortletRequest.RESOURCE_PHASE))) {
+
+				String parameterValue = clientDataRequest.getParameter(formParam.value());
+
+				if (parameterValue == null) {
+					parameterValue = clientDataRequest.getParameter(portletResponse.getNamespace() + formParam.value());
+				}
+
+				if (parameterValue == null) {
+
+					if (LOG.isDebugEnabled()) {
+						LOG.debug("Injecting the default value=" + defaultValue +
+							" into a field annotated with @FormParam(\"" + formParam.value() +
+							"\") because the request " + "parameter does not have a value.");
+					}
+
+					return defaultValue;
+				}
+
+				return parameterValue;
+			}
+
+			if (LOG.isDebugEnabled()) {
+
+				if (lifecyclePhase.equals(PortletRequest.RENDER_PHASE)) {
+					LOG.debug("Injection into a field annotated with " + "@FormParam(\"" + formParam.value() +
+						"\") is " + "invalid during the RENDER_PHASE.");
+				}
+				else {
+					LOG.debug("Injection into a field annotated with " + "@FormParam(\"" + formParam.value() +
+						"\") is " + "invalid during the " + lifecyclePhase + " (HTTP " + httpMethod + ") request.");
+				}
+			}
+
+			return null;
+		}
+
+		HeaderParam headerParam = field.getAnnotation(HeaderParam.class);
+
+		if (headerParam != null) {
+			Enumeration<String> propertyNames = clientDataRequest.getPropertyNames();
+
+			while (propertyNames.hasMoreElements()) {
+				String propertyName = propertyNames.nextElement();
+
+				if (Objects.equals(headerParam.value(), propertyName)) {
+					String headerValue = clientDataRequest.getProperty(propertyName);
+
+					if (headerValue == null) {
+
+						if (LOG.isDebugEnabled()) {
+							LOG.debug("Injecting the default value=" + defaultValue + " into a field annotated " +
+								"with @HeaderParam(\"" + headerParam.value() +
+								"\") because the header does not have a " + "value.");
+						}
+
+						return defaultValue;
+					}
+
+					return headerValue;
+				}
+			}
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("Injecting the default value=" + defaultValue +
+					" into a field annotated with @HeaderParam(\"" + headerParam.value() +
+					"\") because there is no header with that name.");
+			}
+
+			return defaultValue;
+		}
+
+		QueryParam queryParam = field.getAnnotation(QueryParam.class);
+
+		if (queryParam != null) {
+			String parameterValue = clientDataRequest.getParameter(queryParam.value());
+
+			if (parameterValue == null) {
+				parameterValue = clientDataRequest.getParameter(portletResponse.getNamespace() + queryParam.value());
+			}
+
+			if (parameterValue == null) {
+
+				if (LOG.isDebugEnabled()) {
+					LOG.debug("Injecting the default value=" + defaultValue +
+						" into a field annotated with @QueryParam(\"" + queryParam.value() +
+						"\") because the request parameter does not have " + "a value.");
+				}
+
+				return defaultValue;
+			}
+
+			return parameterValue;
+		}
+
+		return defaultValue;
+	}
+
+	private void _addBindingError(Annotation[] fieldAnnotations, String message, String value) {
+
+		if (mutableBindingResult == null) {
+			LOG.error("Unable to add binding error");
+		}
+		else {
+			mutableBindingResult.addBindingError(new BindingErrorImpl(_getParamName(fieldAnnotations), message, value));
+		}
+	}
+
+	private Annotation[] _getFieldAnnotations(Annotated field) {
+		Set<Annotation> annotations = field.getAnnotations();
+
+		return annotations.toArray(new Annotation[annotations.size()]);
+	}
+
+	private <T> ParamConverter _getParamConverter(Class<?> rawType, Type baseType, Annotation[] annotations) {
+
+		for (ParamConverterProvider paramConverterProvider : paramConverterProviders) {
+
+			ParamConverter<T> paramConverter = paramConverterProvider.getConverter((Class<T>) rawType, baseType,
+					annotations);
+
+			if (paramConverter != null) {
+				return paramConverter;
+			}
+		}
+
+		return null;
+	}
+
+	private String _getParamName(Annotation[] annotations) {
+
+		for (Annotation annotation : annotations) {
+			Class<? extends Annotation> annotationClass = annotation.getClass();
+
+			if (CookieParam.class.isAssignableFrom(annotationClass)) {
+				CookieParam cookieParam = (CookieParam) annotation;
+
+				return cookieParam.value();
+			}
+
+			if (FormParam.class.isAssignableFrom(annotationClass)) {
+				FormParam formParam = (FormParam) annotation;
+
+				return formParam.value();
+			}
+
+			if (HeaderParam.class.isAssignableFrom(annotationClass)) {
+				HeaderParam headerParam = (HeaderParam) annotation;
+
+				return headerParam.value();
+			}
+
+			if (QueryParam.class.isAssignableFrom(annotationClass)) {
+				QueryParam queryParam = (QueryParam) annotation;
+
+				return queryParam.value();
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/RequestImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/RequestImpl.java
new file mode 100644
index 0000000..00c5925
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/RequestImpl.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.pluto.container.bean.mvc;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class RequestImpl implements Request {
+
+	@Override
+	public Response.ResponseBuilder evaluatePreconditions() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Response.ResponseBuilder evaluatePreconditions(EntityTag entityTag) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Response.ResponseBuilder evaluatePreconditions(Date date) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Response.ResponseBuilder evaluatePreconditions(Date date, EntityTag entityTag) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public String getMethod() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Variant selectVariant(List<Variant> list) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ResourceInfoImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ResourceInfoImpl.java
new file mode 100644
index 0000000..6a921d3
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ResourceInfoImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.reflect.Method;
+
+import javax.ws.rs.container.ResourceInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ResourceInfoImpl implements ResourceInfo {
+
+	private Method resourceMethod;
+	private Class<?> resourceClass;
+
+	public ResourceInfoImpl(Class<?> resourceClass, Method resourceMethod) {
+		this.resourceClass = resourceClass;
+		this.resourceMethod = resourceMethod;
+	}
+
+	@Override
+	public Class<?> getResourceClass() {
+		return resourceClass;
+	}
+
+	@Override
+	public Method getResourceMethod() {
+		return resourceMethod;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ServletContextProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ServletContextProducer.java
new file mode 100644
index 0000000..b36e20f
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ServletContextProducer.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Alternative;
+import javax.enterprise.inject.Produces;
+import javax.interceptor.Interceptor;
+import javax.servlet.ServletContext;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Alternative
+@ApplicationScoped
+@Priority(Interceptor.Priority.APPLICATION + 10)
+public class ServletContextProducer {
+
+	private Set<ServletContext> servletContexts = new HashSet<>();
+
+	public void applicationScopedInitialized(
+		@Initialized(ApplicationScoped.class)
+		@Observes ServletContext servletContext) {
+		servletContexts.add(servletContext);
+	}
+
+	@Produces
+	public ServletContext getServletContext() {
+
+		Thread currentThread = Thread.currentThread();
+		ClassLoader contextClassLoader = currentThread.getContextClassLoader();
+
+		for (ServletContext servletContext : servletContexts) {
+
+			if (contextClassLoader.equals(servletContext.getClassLoader())) {
+				return servletContext;
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/SpringCsrfToken.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/SpringCsrfToken.java
new file mode 100644
index 0000000..3bbe843
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/SpringCsrfToken.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.reflect.Method;
+
+import javax.enterprise.inject.Vetoed;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Vetoed
+public class SpringCsrfToken {
+
+	private static final Logger LOG = LoggerFactory.getLogger(SpringCsrfToken.class);
+
+	private String name;
+	private String value;
+
+	public SpringCsrfToken(Object springCsrfToken) {
+
+		if (springCsrfToken != null) {
+
+			Class<?> springCsrfTokenClass = springCsrfToken.getClass();
+
+			try {
+				Method getParameterNameMethod = springCsrfTokenClass.getMethod("getParameterName", new Class[] {});
+				getParameterNameMethod.setAccessible(true);
+				name = (String) getParameterNameMethod.invoke(springCsrfToken, new Object[] {});
+
+				Method getTokenMethod = springCsrfTokenClass.getMethod("getToken", null);
+				getTokenMethod.setAccessible(true);
+				value = (String) getTokenMethod.invoke(springCsrfToken, null);
+			}
+			catch (Exception e) {
+				LOG.error(e.getMessage(), e);
+			}
+		}
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getValue() {
+		return value;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/UriInfoImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/UriInfoImpl.java
new file mode 100644
index 0000000..00213f9
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/UriInfoImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class UriInfoImpl implements UriInfo {
+
+	@Override
+	public URI getAbsolutePath() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public UriBuilder getAbsolutePathBuilder() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public URI getBaseUri() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public UriBuilder getBaseUriBuilder() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public List<Object> getMatchedResources() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public List<String> getMatchedURIs() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public List<String> getMatchedURIs(boolean decode) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public String getPath() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public String getPath(boolean decode) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public MultivaluedMap<String, String> getPathParameters() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public List<PathSegment> getPathSegments() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public List<PathSegment> getPathSegments(boolean decode) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public MultivaluedMap<String, String> getQueryParameters() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public URI getRequestUri() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public UriBuilder getRequestUriBuilder() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public URI relativize(URI uri) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public URI resolve(URI uri) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ValidationErrorImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ValidationErrorImpl.java
new file mode 100644
index 0000000..7bc957b
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ValidationErrorImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.binding.ValidationError;
+import javax.validation.ConstraintViolation;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ValidationErrorImpl extends ParamErrorImpl implements ValidationError {
+
+	private ConstraintViolation constraintViolation;
+
+	public ValidationErrorImpl(String paramName, String message, ConstraintViolation constraintViolation) {
+		super(paramName, message);
+		this.constraintViolation = constraintViolation;
+	}
+
+	@Override
+	public ConstraintViolation<?> getViolation() {
+		return constraintViolation;
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextImpl.java
new file mode 100644
index 0000000..59a28ad
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextImpl.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Locale;
+
+import javax.mvc.Models;
+import javax.mvc.engine.ViewEngineContext;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletRequest;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ViewEngineContextImpl implements ViewEngineContext {
+
+	private static final Logger LOG = LoggerFactory.getLogger(ViewEngineContextImpl.class);
+
+	private Configuration configuration;
+	private Locale locale;
+	private MediaType mediaType;
+	private MimeResponse mimeResponse;
+	private Models models;
+	private PortletRequest portletRequest;
+
+	public ViewEngineContextImpl(Configuration configuration, PortletRequest portletRequest, MimeResponse mimeResponse,
+		Models models, Locale locale) {
+		this.configuration = configuration;
+		this.portletRequest = portletRequest;
+		this.mimeResponse = mimeResponse;
+		this.models = models;
+		this.locale = locale;
+	}
+
+	@Override
+	public Configuration getConfiguration() {
+		return configuration;
+	}
+
+	@Override
+	public Locale getLocale() {
+		return locale;
+	}
+
+	@Override
+	public MediaType getMediaType() {
+
+		if (mediaType == null) {
+			String contentType = mimeResponse.getContentType();
+
+			if (contentType == null) {
+				mediaType = MediaType.TEXT_HTML_TYPE;
+			}
+			else {
+
+				String type = contentType;
+				String subtype = null;
+
+				int pos = contentType.indexOf("/");
+
+				if (pos > 0) {
+					type = contentType.substring(0, pos);
+					subtype = contentType.substring(pos + 1);
+				}
+
+				mediaType = new MediaType(type, subtype, mimeResponse.getCharacterEncoding());
+			}
+		}
+
+		return mediaType;
+	}
+
+	@Override
+	public Models getModels() {
+		return models;
+	}
+
+	@Override
+	public OutputStream getOutputStream() {
+
+		try {
+			return mimeResponse.getPortletOutputStream();
+		}
+		catch (IOException e) {
+			LOG.error(e.getMessage(), e);
+
+			return null;
+		}
+	}
+
+	@Override
+	public <T> T getRequest(Class<T> type) {
+		return (T) portletRequest;
+	}
+
+	@Override
+	public ResourceInfo getResourceInfo() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> T getResponse(Class<T> type) {
+		return (T) mimeResponse;
+	}
+
+	@Override
+	public MultivaluedMap<String, Object> getResponseHeaders() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public UriInfo getUriInfo() {
+		return new UriInfoImpl();
+	}
+
+	@Override
+	public String getView() {
+		return (String) portletRequest.getAttribute(ViewRenderer.VIEW_NAME);
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextProducer.java
new file mode 100644
index 0000000..a11d15a
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineContextProducer.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Produces;
+import javax.mvc.engine.ViewEngineContext;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletRequest;
+import javax.ws.rs.core.Configuration;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ViewEngineContextProducer {
+
+	@Dependent
+	@Produces
+	ViewEngineContext getViewEngineContext(Configuration configuration, PortletRequest portletRequest,
+		MimeResponse mimeResponse, ModelsImpl modelsImpl) {
+		return new ViewEngineContextImpl(configuration, portletRequest, mimeResponse, modelsImpl,
+				portletRequest.getLocale());
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineJspImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineJspImpl.java
new file mode 100644
index 0000000..e76bfa7
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngineJspImpl.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.mvc.engine.ViewEngine;
+import javax.mvc.engine.ViewEngineContext;
+import javax.mvc.engine.ViewEngineException;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletResponse;
+import javax.ws.rs.core.Configuration;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public class ViewEngineJspImpl implements ViewEngine {
+
+	private final Configuration configuration;
+	private final PortletContext portletContext;
+
+	public ViewEngineJspImpl(Configuration configuration, PortletContext portletContext) {
+
+		this.configuration = configuration;
+		this.portletContext = portletContext;
+	}
+
+	@Override
+	public void processView(ViewEngineContext viewEngineContext) throws ViewEngineException {
+
+		String view = viewEngineContext.getView();
+
+		String viewFolder = (String) configuration.getProperty(ViewEngine.VIEW_FOLDER);
+
+		if (viewFolder == null) {
+			viewFolder = ViewEngine.DEFAULT_VIEW_FOLDER;
+		}
+
+		String viewPath = viewFolder.concat(view);
+
+		PortletRequestDispatcher requestDispatcher = portletContext.getRequestDispatcher(viewPath);
+
+		try {
+			requestDispatcher.include(viewEngineContext.getRequest(PortletRequest.class),
+				viewEngineContext.getResponse(PortletResponse.class));
+		}
+		catch (Exception e) {
+			throw new ViewEngineException(e);
+		}
+	}
+
+	@Override
+	public boolean supports(String view) {
+		return (view != null) && (view.endsWith(".jsp") || view.endsWith(".jspx"));
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngines.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngines.java
new file mode 100644
index 0000000..6061aac
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEngines.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+public @interface ViewEngines {
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEnginesProducer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEnginesProducer.java
new file mode 100644
index 0000000..ee0e6dd
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewEnginesProducer.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.mvc.engine.ViewEngine;
+import javax.portlet.PortletContext;
+import javax.ws.rs.core.Configuration;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class ViewEnginesProducer {
+
+	@ApplicationScoped
+	@Produces
+	@ViewEngines
+	public List<ViewEngine> getViewEngines(BeanManager beanManager, PortletContext portletContext,
+		Configuration configuration) {
+
+		List<ViewEngine> viewEngines = BeanUtil.getBeanInstances(beanManager, ViewEngine.class);
+
+		viewEngines.add(new ViewEngineJspImpl(configuration, portletContext));
+
+		Collections.sort(viewEngines, new DescendingPriorityComparator(ViewEngine.PRIORITY_APPLICATION));
+
+		return viewEngines;
+	}
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRenderer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRenderer.java
new file mode 100644
index 0000000..d5835ca
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRenderer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+
+
+/**
+ * @author  Neil Griffin
+ */
+public interface ViewRenderer {
+
+	public static final String REDIRECT_PREFIX = "redirect:";
+
+	public static final String REDIRECTED_VIEW = "redirectedView";
+
+	public static final String VIEW_NAME = "viewName";
+
+	public void render(PortletRequest portletRequest, MimeResponse mimeResponse, PortletConfig portletConfig)
+		throws PortletException;
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRendererMvcImpl.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRendererMvcImpl.java
new file mode 100644
index 0000000..d01f741
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/mvc/ViewRendererMvcImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pluto.container.bean.mvc;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.mvc.Models;
+import javax.mvc.binding.ParamError;
+import javax.mvc.engine.ViewEngine;
+import javax.mvc.engine.ViewEngineException;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.ws.rs.core.Configuration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author  Neil Griffin
+ */
+@ApplicationScoped
+public class ViewRendererMvcImpl implements ViewRenderer {
+
+	private static final Logger LOG = LoggerFactory.getLogger(ViewRendererMvcImpl.class);
+
+	@Inject
+	private BeanManager beanManager;
+
+	@Inject
+	private Configuration configuration;
+
+	@Inject
+	private Models models;
+
+	@Inject
+	private MutableBindingResult mutableBindingResult;
+
+	@Inject
+	@ViewEngines
+	private List<ViewEngine> viewEngines;
+
+	@Override
+	public void render(PortletRequest portletRequest, MimeResponse mimeResponse, PortletConfig portletConfig)
+		throws PortletException {
+
+		Map<String, Object> modelMap = models.asMap();
+
+		for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
+			portletRequest.setAttribute(entry.getKey(), entry.getValue());
+		}
+
+		String viewName = (String) portletRequest.getAttribute(VIEW_NAME);
+
+		if (viewName != null) {
+
+			if (!viewName.contains(".")) {
+				String defaultViewExtension = (String) configuration.getProperty(
+						ConfigurationImpl.DEFAULT_VIEW_EXTENSION);
+
+				viewName = viewName.concat(".").concat(defaultViewExtension);
+			}
+
+			ViewEngine supportingViewEngine = null;
+
+			for (ViewEngine viewEngine : viewEngines) {
+
+				if (viewEngine.supports(viewName)) {
+					supportingViewEngine = viewEngine;
+
+					break;
+				}
+			}
+
+			if (supportingViewEngine == null) {
+				throw new PortletException(new ViewEngineException("No ViewEngine found that supports " + viewName));
+			}
+
+			try {
+				beanManager.fireEvent(new BeforeProcessViewEventImpl(viewName, supportingViewEngine.getClass()));
+
+				supportingViewEngine.processView(new ViewEngineContextImpl(configuration, portletRequest, mimeResponse,
+						models, portletRequest.getLocale()));
+
+				beanManager.fireEvent(new AfterProcessViewEventImpl(viewName, supportingViewEngine.getClass()));
+			}
+			catch (ViewEngineException vee) {
+				throw new PortletException(vee);
+			}
+		}
+
+		if ((mutableBindingResult != null) && !mutableBindingResult.isConsulted()) {
+
+			Set<ParamError> allErrors = mutableBindingResult.getAllErrors();
+
+			for (ParamError paramError : allErrors) {
+
+				if (LOG.isWarnEnabled()) {
+					LOG.warn("BindingResult error not processed for " + paramError.getParamName() + ": " +
+						paramError.getMessage());
+				}
+			}
+		}
+	}
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/AnnotatedConfigBean.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/AnnotatedConfigBean.java
index 4944dcd..dff76f4 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/AnnotatedConfigBean.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/AnnotatedConfigBean.java
@@ -32,6 +32,7 @@ public class AnnotatedConfigBean {
 
    private AnnotatedMethodStore methodStore = null;
    private ConfigSummary summary = null;
+   private RedirectScopedConfig redirectScopedConfig = null;
    private PortletStateScopedConfig stateScopedConfig = null;
    private PortletSessionScopedConfig sessionScopedConfig = null;
    
@@ -64,6 +65,20 @@ public class AnnotatedConfigBean {
    }
 
    /**
+    * @return the redirectScopedConfig
+    */
+   public RedirectScopedConfig getRedirectScopedConfig() {
+      return redirectScopedConfig;
+   }
+
+   /**
+    * @param redirectScopedConfig the redirectScopedConfig to set
+    */
+   public void setRedirectScopedConfig(RedirectScopedConfig redirectScopedConfig) {
+      this.redirectScopedConfig = redirectScopedConfig;
+   }
+
+   /**
     * @return the stateScopedConfig
     */
    public PortletStateScopedConfig getStateScopedConfig() {
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodAnnotationRecognizer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodAnnotationRecognizer.java
index 985fede..af18dc9 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodAnnotationRecognizer.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodAnnotationRecognizer.java
@@ -59,7 +59,11 @@ public class MethodAnnotationRecognizer extends PortletAnnotationRecognizer impl
 
 
    public MethodAnnotationRecognizer(AnnotatedMethodStore pms, ConfigSummary summary) {
-      super(pms, summary);
+      this(pms, summary, true);
+   }
+
+   public MethodAnnotationRecognizer(AnnotatedMethodStore pms, ConfigSummary summary, boolean mvc) {
+      super(pms, summary, mvc);
       ad = new AnnotationDetector(this);
    }
 
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodDescription.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodDescription.java
index f6c30a6..916cc62 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodDescription.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/MethodDescription.java
@@ -110,15 +110,18 @@ public class MethodDescription {
    
    // If true, multiple methods of this type are allowed
    private boolean            allowMultiple = false;
+
+   // If true, it is a method signature required for MVC
+   private boolean            mvc = false;
    
    // Message string used during method matching.
    private final StringBuilder errtxt = new StringBuilder(128);
 
    public MethodDescription(Class<?> rt, Class<?>[] at, Class<?>[] et, MethodType mt) {
-      retType = rt;
-      argTypes = at;
-      expTypes = et;
-      type = mt;
+      this.retType = rt;
+      this.argTypes = at;
+      this.expTypes = et;
+      this.type = mt;
    }
    
    /**
@@ -238,7 +241,14 @@ public class MethodDescription {
 
       return ok;
    }
-   
+
+   public boolean isMvc() {
+      return mvc;
+   }
+
+   public void setMvc(boolean mvc) {
+      this.mvc = mvc;
+   }
 
    /**
     * Returns the expected method signature in string form, optionally
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletAnnotationRecognizer.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletAnnotationRecognizer.java
index eaba9a2..a56aab2 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletAnnotationRecognizer.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletAnnotationRecognizer.java
@@ -27,6 +27,7 @@ import static org.apache.pluto.container.bean.processor.MethodDescription.METH_R
 import static org.apache.pluto.container.bean.processor.MethodDescription.METH_DES;
 import static org.apache.pluto.container.bean.processor.MethodDescription.METH_INI;
 
+import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -41,8 +42,15 @@ import javax.enterprise.context.RequestScoped;
 import javax.enterprise.context.SessionScoped;
 import javax.enterprise.inject.spi.AnnotatedType;
 import javax.enterprise.inject.spi.BeanManager;
+import javax.mvc.RedirectScoped;
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
 import javax.portlet.PortletException;
 import javax.portlet.PortletSession;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
 import javax.portlet.annotations.ActionMethod;
 import javax.portlet.annotations.DestroyMethod;
 import javax.portlet.annotations.EventMethod;
@@ -69,6 +77,7 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
          new HashSet<Class<? extends Annotation>>();
    
    static {
+      classAnnotations.add(RedirectScoped.class);
       classAnnotations.add(RenderStateScoped.class);
       classAnnotations.add(PortletSessionScoped.class);
       classAnnotations.add(SessionScoped.class);
@@ -87,34 +96,52 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
 
       // Add method definitions for init methods
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       list.add(METH_INI);
       descriptions.put(InitMethod.class, list);
       
       // Add method definitions for destroy methods
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       list.add(METH_DES);
       descriptions.put(DestroyMethod.class, list);
       
       // Add method definitions for action methods
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       list.add(METH_ACT);
+
+      md = new MethodDescription(String.class,
+          new Class<?>[] {ActionRequest.class, ActionResponse.class},
+          new Class<?>[] {PortletException.class, java.io.IOException.class},
+          MethodType.ACTION);
+      md.setMvc(true);
+      md.setVariant(SignatureVariant.STRING_ACTIONREQ_ACTIONRESP);
+      list.add(md);
+
       descriptions.put(ActionMethod.class, list);
       
       // Add method definitions for event methods
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       list.add(METH_EVT);
       descriptions.put(EventMethod.class, list);
       
       // Add method definitions for serve resource
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       
       list.add(METH_RES);
-      
+
+      md = new MethodDescription(String.class,
+          new Class<?>[] {ResourceRequest.class, ResourceResponse.class},
+          new Class<?>[] {PortletException.class, java.io.IOException.class},
+          MethodType.RESOURCE);
+      md.setAllowMultiple(true);
+      md.setMvc(true);
+      md.setVariant(SignatureVariant.STRING_RESOURCEREQ_RESOURCERESP);
+      list.add(md);
+
       md = new MethodDescription(String.class, 
             new Class<?>[] {},  
             new Class<?>[] {PortletException.class, java.io.IOException.class}, 
@@ -135,16 +162,25 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
       
       // Add method definitions for render
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       
       list.add(METH_REN);
-      
+
+      md = new MethodDescription(String.class,
+          new Class<?>[] {},
+          new Class<?>[] {PortletException.class, java.io.IOException.class},
+          MethodType.RENDER);
+      md.setAllowMultiple(true);
+      md.setVariant(SignatureVariant.STRING_VOID);
+      list.add(md);
+
       md = new MethodDescription(String.class, 
-            new Class<?>[] {},  
+            new Class<?>[] {RenderRequest.class, RenderResponse.class},
             new Class<?>[] {PortletException.class, java.io.IOException.class}, 
             MethodType.RENDER);
       md.setAllowMultiple(true);
-      md.setVariant(SignatureVariant.STRING_VOID);
+      md.setMvc(true);
+      md.setVariant(SignatureVariant.STRING_RENDERREQ_RENDERRESP);
       list.add(md);
       
       md = new MethodDescription(void.class, 
@@ -159,7 +195,7 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
       
       // Add method definitions for header methods
       
-      list = new ArrayList<MethodDescription>();
+      list = new ArrayList<>();
       list.add(METH_HDR);
       
       md = new MethodDescription(String.class, 
@@ -183,19 +219,50 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
    }
 
    private final AnnotatedMethodStore ams;
+   private final boolean mvc;
+   private final RedirectScopedConfig redirectScopedConfig = new RedirectScopedConfig();
    private final PortletStateScopedConfig stateScopedConfig = new PortletStateScopedConfig();
    private final PortletSessionScopedConfig sessionScopedConfig = new PortletSessionScopedConfig();
-   
+
    /**
-    * @param cla
-    * @param metha
+    * @param pms
+    * @param summary
     */
    public PortletAnnotationRecognizer(AnnotatedMethodStore pms, ConfigSummary summary) {
-      super(classAnnotations, descriptions, summary);
+      this(pms, summary, true);
+   }
+
+   /**
+    * @param pms
+    * @param summary
+    */
+   public PortletAnnotationRecognizer(AnnotatedMethodStore pms, ConfigSummary summary, boolean mvc) {
+      super(classAnnotations, filterDescriptions(descriptions, mvc), summary);
       this.ams = pms;
+      this.mvc = mvc;
       LOG.trace("Created the PortletAnnotationRecognizer.");
    }
 
+   private static Map<Class<? extends Annotation>,List<MethodDescription>> filterDescriptions(Map<Class<? extends Annotation>, List<MethodDescription>> descriptions, boolean mvc) {
+      if (mvc) {
+         return descriptions;
+      }
+
+      Map<Class<? extends Annotation>, List<MethodDescription>> filteredDescriptions = new HashMap<>();
+
+      for (Map.Entry<Class<? extends Annotation>, List<MethodDescription>> entry : descriptions.entrySet()) {
+      	 List<MethodDescription> filteredMethodDescriptions = new ArrayList<>();
+         for (MethodDescription methodDescription: entry.getValue()) {
+            if (!methodDescription.isMvc()) {
+               filteredMethodDescriptions.add(methodDescription);
+            }
+         }
+         filteredDescriptions.put(entry.getKey(), filteredMethodDescriptions);
+      }
+
+      return filteredDescriptions;
+   }
+
    /**
     * Extracts data from the class annotation and stores it for later use.
     * <p>
@@ -209,8 +276,24 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
    protected AnnotatedType<?> handleClassAnnotation(Annotation anno, AnnotatedType<?> aType) throws InvalidAnnotationException {
       String typeName = aType.getJavaClass().getCanonicalName();
       Class<?> theClass = aType.getJavaClass();
-      
-      if (anno instanceof RenderStateScoped) {
+
+      if (anno instanceof RedirectScoped) {
+
+         // Verify the redirect scoped class, store the annotation &
+         // bean type with the corresponding bean holder.
+
+         if (!Serializable.class.isAssignableFrom(theClass)) {
+            StringBuilder txt = new StringBuilder(128);
+            txt.append("Annotation problem: An @RedirectScoped bean must implement java.io.Serializable.");
+            txt.append("Annotation: ").append(anno.annotationType().getSimpleName());
+            txt.append(", Class: ").append(typeName);
+            LOG.debug(txt.toString());
+            summary.addStateBeanErrorString(theClass, txt.toString());
+         } else {
+            redirectScopedConfig.addAnnotation(theClass, (RedirectScoped) anno);
+         }
+	  }
+      else if (anno instanceof RenderStateScoped) {
          
          // Verify the render state scoped class, store the annotation &
          // bean type with the corresponding bean holder.
@@ -387,6 +470,7 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
    protected void activateCustomScopes(BeanManager bm) {
       
       // Activate the custom scoped beans
+      redirectScopedConfig.activate(bm);
       stateScopedConfig.activate(bm);
       sessionScopedConfig.activate(bm);
    }
@@ -394,6 +478,13 @@ public class PortletAnnotationRecognizer extends AnnotationRecognizer {
    /**
     * @return the stateScopedConfig
     */
+   public RedirectScopedConfig getRedirectScopedConfig() {
+      return redirectScopedConfig;
+   }
+
+   /**
+    * @return the stateScopedConfig
+    */
    public PortletStateScopedConfig getStateScopedConfig() {
       return stateScopedConfig;
    }
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletCDIExtension.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletCDIExtension.java
index 46f8d95..f9d3c37 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletCDIExtension.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletCDIExtension.java
@@ -39,6 +39,7 @@ import javax.enterprise.inject.spi.Extension;
 import javax.enterprise.inject.spi.ProcessAnnotatedType;
 import javax.portlet.PortletConfig;
 
+import org.apache.pluto.container.bean.mvc.MvcExtension;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,6 +73,7 @@ public class PortletCDIExtension implements Extension {
     */
    void processType(@Observes ProcessAnnotatedType<?> pat) throws InvalidAnnotationException {
       par.checkAnnotatedType(pat);
+      MvcExtension.processAnnotatedType(pat);
    }
    
    
@@ -154,6 +156,9 @@ public class PortletCDIExtension implements Extension {
     * @param abd
     */
    void addPortletCustomScopeContexts(@Observes AfterBeanDiscovery abd) {
+      RedirectScopedContext rsc = new RedirectScopedContext();
+      abd.addContext(rsc);
+
       PortletSessionScopedContext pssc = new PortletSessionScopedContext();
       abd.addContext(pssc);
       
@@ -191,6 +196,7 @@ public class PortletCDIExtension implements Extension {
             LOG.trace("Got AnnotatedConfigBean instance.");
             acb.setMethodStore(ams);
             acb.setSummary(summary);
+            acb.setRedirectScopedConfig(par.getRedirectScopedConfig());
             acb.setStateScopedConfig(par.getStateScopedConfig());
             acb.setSessionScopedConfig(par.getSessionScopedConfig());
          } catch (Exception e) {
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletInvoker.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletInvoker.java
index 6726440..e81dbac 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletInvoker.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletInvoker.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.ResourceBundle;
 
+import javax.portlet.ActionParameters;
 import javax.portlet.ActionRequest;
 import javax.portlet.ActionResponse;
 import javax.portlet.EventPortlet;
@@ -60,6 +61,9 @@ import org.slf4j.LoggerFactory;
  *
  */
 public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPortlet, HeaderPortlet {
+
+   public static final String LAST_METHOD = PortletInvoker.class.getName() + ".LAST_METHOD";
+
    private static final Logger       LOG     = LoggerFactory.getLogger(PortletInvoker.class);
    private static final boolean      isDebug = LOG.isDebugEnabled();
 
@@ -252,7 +256,8 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
          meths = getMethods(mi);
       }
 
-      if (meths.size() == 0) {
+      int totalMethods = meths.size();
+      if (totalMethods == 0) {
 
          // If a resource URL was activated, but no resource method could be
          // found, add appropriate error string.
@@ -265,9 +270,14 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
 
       // now do the invocation
 
-      for (AnnotatedMethod meth : meths) {
+      for (int i = 0; i < totalMethods; i++) {
+
+         if (i == (totalMethods - 1)) {
+            req.setAttribute(LAST_METHOD, Boolean.TRUE);
+         }
 
          // Set the character encoding & content type, if available
+         AnnotatedMethod meth = meths.get(i);
 
          ServeResourceMethod rm = (ServeResourceMethod) meth.getAnnotation();
          if (rm != null) {
@@ -283,7 +293,10 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
 
          Object[] args = {};
          Object result;
-         if (meth.getDescription().getVariant() == SignatureVariant.VOID_RESOURCEREQ_RESOURCERESP) {
+         MethodDescription methDescription = meth.getDescription();
+         SignatureVariant signatureVariant = methDescription.getVariant();
+         if ((signatureVariant == SignatureVariant.STRING_RESOURCEREQ_RESOURCERESP) ||
+             (signatureVariant == SignatureVariant.VOID_RESOURCEREQ_RESOURCERESP)) {
             args = new Object[] { req, resp };
          }
 
@@ -308,7 +321,7 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
             // No async processing.
             // If output is to be expected, write it to the writer
 
-            if (meth.getDescription().getVariant() == SignatureVariant.STRING_VOID) {
+            if (signatureVariant == SignatureVariant.STRING_VOID) {
                if (result != null) {
                   assert result instanceof String;
                   resp.getWriter().write((String) result);
@@ -361,7 +374,14 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
    @Override
    public void processAction(ActionRequest req, ActionResponse resp) throws PortletException, IOException {
 
-      String an = req.getActionParameters().getValue(ACTION_NAME);
+      ActionParameters actionParameters = req.getActionParameters();
+
+      String an = actionParameters.getValue(ACTION_NAME);
+
+      if (an == null) {
+         an = actionParameters.getValue(resp.getNamespace() + ACTION_NAME);
+      }
+
       String id = (an != null) ? an : "";
       MethodIdentifier mi = new MethodIdentifier(portletName, id, MethodType.ACTION);
 
@@ -409,10 +429,16 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
          return;
       }
 
-      for (AnnotatedMethod meth : meths) {
+      int totalMethods = meths.size();
+      for (int i = 0; i < totalMethods; i++) {
+
+         if (i == (totalMethods - 1)) {
+            req.setAttribute(LAST_METHOD, Boolean.TRUE);
+         }
 
          // Set the content type, if available (determined by first render
          // method)
+         AnnotatedMethod meth = meths.get(i);
 
          RenderMethod rm = (RenderMethod) meth.getAnnotation();
          if ((rm != null) && !rm.contentType().matches("(^$|\\*|\\*/\\*|text/\\*)")) {
@@ -423,7 +449,10 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
 
          Object[] args = {};
          Object result;
-         if (meth.getDescription().getVariant() == SignatureVariant.VOID_RENDERREQ_RENDERRESP) {
+         MethodDescription methodDescription = meth.getDescription();
+         SignatureVariant variant = methodDescription.getVariant();
+         if ((variant == SignatureVariant.DEFAULT.STRING_RENDERREQ_RENDERRESP) ||
+             (variant == SignatureVariant.VOID_RENDERREQ_RENDERRESP)) {
             args = new Object[] { req, resp };
          }
 
@@ -433,7 +462,7 @@ public class PortletInvoker implements Portlet, ResourceServingPortlet, EventPor
 
          // If output is to be expected, write it to the writer
 
-         if (meth.getDescription().getVariant() == SignatureVariant.STRING_VOID) {
+         if (variant == SignatureVariant.STRING_VOID) {
             if (result != null) {
                assert result instanceof String;
                resp.getWriter().write((String) result);
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletSessionScopedBeanMap.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletSessionScopedBeanMap.java
index c049c88..851efd3 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletSessionScopedBeanMap.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/PortletSessionScopedBeanMap.java
@@ -200,4 +200,23 @@ public class PortletSessionScopedBeanMap implements HttpSessionBindingListener,
          beans.clear();
       }
    }
+
+   public void removeByWindowId(String windowId) {
+
+      if (isTrace) {
+         LOG.trace("Removing beans associated with windowId=" + windowId);
+      }
+
+      synchronized(beans) {
+         for (String curWindowId : beans.keySet()) {
+         	if (curWindowId.equals(windowId)) {
+               Map<Contextual<?>, BeanInstance<?>> beanMap =
+                   beans.get(curWindowId);
+               for (Contextual<?> bean : beanMap.keySet()) {
+                  remove(curWindowId, beanMap, bean);
+               }
+            }
+         }
+      }
+   }
 }
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedBeanHolder.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedBeanHolder.java
new file mode 100644
index 0000000..e679d0b
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedBeanHolder.java
@@ -0,0 +1,176 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.pluto.container.bean.processor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import java.io.Serializable;
+
+/**
+ * This is a container for RedirectScoped CDI beans.
+ * 
+ * @author Scott Nicklous
+ * @author Neil Griffin
+ *
+ */
+public class RedirectScopedBeanHolder implements Serializable {
+   private static final Logger  LOG              = LoggerFactory
+                                                       .getLogger(RedirectScopedBeanHolder.class);
+   private static final boolean isTrace          = LOG.isTraceEnabled();
+
+   private static final long    serialVersionUID = 3856727894564632122L;
+   private static final String  ATTRIBNAME       = "redirectScopedBeanHolder";
+
+   // The ThreadLocal manages the holders so that there is one holder per thread.
+   private static final ThreadLocal<RedirectScopedBeanHolder> holders = new ThreadLocal<>();
+
+   // ========== Instance vars
+
+   private final PortletSessionScopedBeanMap beans;
+   private final String                      windowId;
+
+   /**
+    * private constructor ... constructed once per thread
+    */
+   private RedirectScopedBeanHolder(PortletSessionScopedBeanMap beans, String windowId) {
+      this.beans = beans;
+      this.windowId = windowId;
+   }
+
+   /**
+    * Sets the redirect scope bean holder in a ThreadLocal object for the portlet session associated with the given request. If no bean holder
+    * exists in the session, a new one is created.
+    * 
+    * @param req The portlet request.
+    * @return The redirect scope bean holder
+    */
+   public static void setBeanHolder(PortletRequest req) {
+
+      PortletSession ps = req.getPortletSession();
+      String windowId = req.getWindowID();
+
+      PortletSessionScopedBeanMap map = (PortletSessionScopedBeanMap) ps.getAttribute(ATTRIBNAME,
+            PortletSession.APPLICATION_SCOPE);
+      
+      boolean createdMap = false;
+      if (map == null) {
+         map = new PortletSessionScopedBeanMap();
+         ps.setAttribute(ATTRIBNAME, map, PortletSession.APPLICATION_SCOPE);
+         createdMap = true;
+      }
+
+      RedirectScopedBeanHolder
+		  holder = new RedirectScopedBeanHolder(map, windowId);
+      holders.set(holder);
+
+      if (isTrace) {
+         StringBuilder txt = new StringBuilder(80);
+         txt.append("Set redirect scope bean holder.");
+         txt.append(" ThreadId: ").append(Thread.currentThread().getId());
+         txt.append(", PortletSession: ").append(ps.getId());
+         txt.append(", WindowId: ").append(windowId);
+         txt.append(", Added new BeanMap to session: ").append(createdMap);
+         LOG.debug(txt.toString());
+      }
+   }
+
+   /**
+    * Removes the bean holder for the current session.
+    * @param destroyBeans Flag indicating whether or not the beans stored in the session should be destroyed.
+    */
+   public static void removeBeanHolder(boolean destroyBeans) {
+
+      if (destroyBeans) {
+        RedirectScopedBeanHolder holder = holders.get();
+        holder.removeBeans();
+      }
+      holders.remove();
+
+      if (isTrace) {
+         StringBuilder txt = new StringBuilder(80);
+         txt.append("Removed redirect scope bean holder.");
+         txt.append(" ThreadId=").append(Thread.currentThread().getId());
+         LOG.debug(txt.toString());
+      }
+   }
+
+   /**
+    * Returns the redirect scope bean holder that was set for the thread.
+    * 
+    * @return
+    */
+   public static RedirectScopedBeanHolder getBeanHolder() {
+      return holders.get();
+   }
+   
+   /**
+    * Removes the bean holder for the current thread and
+    * returns the removed instance to the caller.
+    * 
+    * @return  the removed bean holder
+    */
+   public static RedirectScopedBeanHolder deregister() {
+      RedirectScopedBeanHolder holder = holders.get();
+      holders.remove();
+      return holder;
+   }
+   
+   /**
+    * Registers the provided bean holder for the current thread.
+    * 
+    * @param holder the bean holder to register
+    */
+   public static void register(RedirectScopedBeanHolder holder) {
+      holders.set(holder);
+   }
+
+   /**
+    * Returns an instance for the contextual type, or null if none available.
+    * 
+    * @param bean
+    *           Contextual type (Bean) for which an instance is desired
+    * @return The instance, or null if none exists
+    */
+   public <T> T getBean(Contextual<T> bean) {
+      return beans.getBeanInstance(windowId, bean);
+   }
+
+   /**
+    * Adds a bean instance with associated creational context to the store for the given contextual object (bean).
+    * 
+    * @param bean
+    *           The bean type
+    * @param crco
+    *           The creational context
+    * @param instance
+    *           The bean instance
+    */
+   public <T> void putBeanInstance(Contextual<T> bean, CreationalContext<T> crco, T instance) {
+      beans.putBeanInstance(windowId, bean, crco, instance);
+   }
+
+   private void removeBeans() {
+   	  beans.removeByWindowId(windowId);
+   }
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedConfig.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedConfig.java
new file mode 100644
index 0000000..81bd0d6
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedConfig.java
@@ -0,0 +1,146 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+
+package org.apache.pluto.container.bean.processor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.mvc.RedirectScoped;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Configuration for RedirectScoped CDI beans.
+ * 
+ * @author Scott Nicklous
+ * @author Neil Griffin
+ *
+ */
+public class RedirectScopedConfig implements Serializable {
+   private static final long serialVersionUID = 4387223464722335532L;
+   private final Logger LOG = LoggerFactory.getLogger(RedirectScopedConfig.class);
+   private final boolean isTrace = LOG.isTraceEnabled();
+   
+   
+   // Contains a sorted list of RedirectScoped annotated class names. The sorted list
+   // is used to generate indexes for the assigned render parameter names.
+   private final List<String> sortedAnnotatedClassNames = new ArrayList<>();
+   
+   // Description for the RedirectScoped bean
+   private class PSSDescription implements Serializable {
+      private static final long serialVersionUID = 3289471768384711112L;
+      RedirectScoped          pssAnno;
+   }
+   
+   // Maps the bean contextual to the annotation. The bean contextual is obtained
+   // during the activation process after all beans have been discovered.
+   // Note that synchronization is not needed since the map is only changed during the
+   // bean scanning phase.
+   private final Map<Contextual<?>, PSSDescription> context2Anno = new HashMap<>();
+
+   // Maps the bean class to the corresponding annotation. The entries are set
+   // while the extension is processing annotated types.
+   // Note that synchronization is not needed since the map is only changed during the
+   // bean scanning phase.
+   private final Map<Class<?>, PSSDescription> class2Anno = new HashMap<>();
+   
+   /**
+    * Called by the CDI extension during the scanning phase to add information about 
+    * a <code>{@literal @}RedirectScoped</code> bean.
+    * 
+    * @param beanClass     The bean class
+    * @param anno          The annotation
+    */
+   public void addAnnotation(Class<?> beanClass, RedirectScoped anno) {
+      PSSDescription desc = new PSSDescription();
+      desc.pssAnno = anno;
+      class2Anno.put(beanClass, desc);
+   }
+   
+   /** 
+    * Gets the concrete contextual for the bean and puts it into the context map.
+    * Called after bean discovery during the activation process.
+    * Finishes up the configuration process and provides a debug summary.
+    * 
+    * @param bm      The bean manager
+    */
+   public void activate(BeanManager bm) {
+      
+      // The assigned render parameters are based on the alphabetic order of the PSS bean 
+      // class names, so generate such a list.
+      
+      for (Class<?> c : class2Anno.keySet()) {
+         sortedAnnotatedClassNames.add(c.getCanonicalName());
+      }
+      Collections.sort(sortedAnnotatedClassNames);
+      
+      // Activate the beans
+      
+      for (Class<?> cls : class2Anno.keySet()) {
+         Set<Bean<?>> beans = bm.getBeans(cls);
+         Bean<?> bean = bm.resolve(beans);
+         assert bean != null;
+         context2Anno.put(bean, class2Anno.get(cls));
+      }
+      
+      // dump configuration data to trace
+      
+      if (isTrace) {
+         StringBuilder txt = new StringBuilder(128);
+         txt.append("RedirectScopedBeanHolder configuration.");
+         txt.append(" Annotated Beans: ");
+         txt.append(getConfigAsString());
+         LOG.debug(txt.toString());
+      }
+   }
+   
+   /**
+    * Returns the redirect scoped annotated classes.
+    * <p>
+    * Used for test / validation purposes.
+    * 
+    * @return  Set of annotated classes
+    */
+   public Set<Class<?>> getBeanClasses() {
+      return class2Anno.keySet();
+   }
+   
+   /**
+    * Returns a redirect scoped bean summary for display
+    * 
+    * @return  The configuration summary string
+    * 
+    */
+   public String getConfigAsString() {
+      StringBuilder txt = new StringBuilder(128);
+      for (Class<?> c : class2Anno.keySet()) {
+         txt.append("\n\tClass: ").append(c.getCanonicalName());
+      }
+      return txt.toString();
+   }
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedContext.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedContext.java
new file mode 100644
index 0000000..dcc60b3
--- /dev/null
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/RedirectScopedContext.java
@@ -0,0 +1,87 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+
+package org.apache.pluto.container.bean.processor;
+
+import javax.enterprise.context.ContextNotActiveException;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.mvc.RedirectScoped;
+import java.lang.annotation.Annotation;
+
+/**
+ * This is the Context implementation for the RedirectScoped custom CDI scope.
+ * 
+ * @author nick
+ * @author Neil Griffin
+ *
+ */
+public class RedirectScopedContext implements Context {
+
+   /* (non-Javadoc)
+    * @see javax.enterprise.context.spi.Context#get(javax.enterprise.context.spi.Contextual)
+    */
+   @Override
+   public <T> T get(Contextual<T> bean) {
+      RedirectScopedBeanHolder holder = RedirectScopedBeanHolder.getBeanHolder();
+      if (holder == null) {
+         throw new ContextNotActiveException("The redirect context is not active.");
+      }
+      return holder.getBean(bean);
+   }
+
+   /* (non-Javadoc)
+    * @see javax.enterprise.context.spi.Context#get(javax.enterprise.context.spi.Contextual, javax.enterprise.context.spi.CreationalContext)
+    */
+   @Override
+   public <T> T get(Contextual<T> bean, CreationalContext<T> crco) {
+      RedirectScopedBeanHolder holder = RedirectScopedBeanHolder.getBeanHolder();
+      
+      if (holder == null) {
+         throw new ContextNotActiveException("The redirect context is not active.");
+      }
+      
+      T inst = holder.getBean(bean);
+      if (inst == null) {
+         inst = bean.create(crco);
+         holder.putBeanInstance(bean, crco, inst);
+      }
+
+      return inst;
+   }
+
+   /* (non-Javadoc)
+    * @see javax.enterprise.context.spi.Context#getScope()
+    */
+   @Override
+   public Class<? extends Annotation> getScope() {
+      return RedirectScoped.class;
+   }
+
+   /* (non-Javadoc)
+    * @see javax.enterprise.context.spi.Context#isActive()
+    */
+   @Override
+   public boolean isActive() {
+      RedirectScopedBeanHolder holder = RedirectScopedBeanHolder.getBeanHolder();
+      return (holder != null);
+   }
+
+}
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/SignatureVariant.java b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/SignatureVariant.java
index d37e076..4bc91ad 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/SignatureVariant.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/bean/processor/SignatureVariant.java
@@ -31,7 +31,31 @@ public enum SignatureVariant {
     * The default signature for the method.
     */
    DEFAULT,
-   
+
+   /**
+    * Signature variant for ActionMethod, with return value String and
+    * taking a ActionRequest and ActionResponse as arguments.
+    */
+   STRING_ACTIONREQ_ACTIONRESP,
+
+   /**
+    * Signature variant for RenderMethod, with return value String and
+    * taking a RenderRequest and RenderResponse as arguments.
+    */
+   STRING_RENDERREQ_RENDERRESP,
+
+   /**
+    * Signature variant for ResourceMethod, with return value String and
+    * taking a ResourceRequest and ResourceResponse as arguments.
+    */
+   STRING_RESOURCEREQ_RESOURCERESP,
+
+   /**
+    * Signature variant for render, header, and resource methods, with return
+    * value String and taking no arguments.
+    */
+   STRING_VOID,
+
    /**
     * Signature variant for HeaderMethod, with return value void and 
     * taking a HeaderRequest and HeaderResponse as arguments.
@@ -49,13 +73,7 @@ public enum SignatureVariant {
     * taking a ResourceRequest and ResourceResponse as arguments.
     */
    VOID_RESOURCEREQ_RESOURCERESP,
-   
-   /**
-    * Signature variant for render, header, and resource methods, with return 
-    * value String and taking no arguments.
-    */
-   STRING_VOID,
-   
+
    /**
     * Signature variant for render, header, and resource methods, with return 
     * value void and taking no arguments.
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/om/portlet/impl/ConfigurationHolder.java b/pluto-container/src/main/java/org/apache/pluto/container/om/portlet/impl/ConfigurationHolder.java
index e345709..bd4d97e 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/om/portlet/impl/ConfigurationHolder.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/om/portlet/impl/ConfigurationHolder.java
@@ -338,13 +338,24 @@ public class ConfigurationHolder {
    }
    
    /**
+    * Scans list of files for method annotations that do not conform to MVC signatures.
+    * (unit test execution path)
+    *
+    * @param files   the set of files to scan
+    */
+   public void scanMethodAnnotations(Set<File> files) {
+      scanMethodAnnotations(files, false);
+   }
+
+   /**
     * Scans list of files for method annotations.
     * (unit test execution path)
     * 
     * @param files   the set of files to scan
+    * @param mvc     flag indicating whether or not MVC method signatures are permitted
     */
-   public void scanMethodAnnotations(Set<File> files) {
-      MethodAnnotationRecognizer mar = new MethodAnnotationRecognizer(methodStore, configSummary);
+   public void scanMethodAnnotations(Set<File> files, boolean mvc) {
+      MethodAnnotationRecognizer mar = new MethodAnnotationRecognizer(methodStore, configSummary, mvc);
       mar.scanFiles(files);
       LOG.debug("Scan complete: \n" + methodStore.getMethodsAsString());
    }
diff --git a/pluto-container/src/main/resources/META-INF/services/javax.ws.rs.ext.RuntimeDelegate b/pluto-container/src/main/resources/META-INF/services/javax.ws.rs.ext.RuntimeDelegate
new file mode 100644
index 0000000..806cbbd
--- /dev/null
+++ b/pluto-container/src/main/resources/META-INF/services/javax.ws.rs.ext.RuntimeDelegate
@@ -0,0 +1 @@
+org.apache.pluto.container.bean.mvc.MvcRuntimeDelegate
\ No newline at end of file
diff --git a/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/fixtures/RedirectScopedClass.java b/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/fixtures/RedirectScopedClass.java
new file mode 100644
index 0000000..9a977e6
--- /dev/null
+++ b/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/fixtures/RedirectScopedClass.java
@@ -0,0 +1,35 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+
+package org.apache.pluto.container.bean.processor.fixtures;
+
+import javax.mvc.RedirectScoped;
+import java.io.Serializable;
+
+/**
+ * Verifies that an {@literal @}RedirectScoped bean is recognized.
+ *
+ * @author Scott Nicklous
+ * @author Neil Griffin
+ */
+@RedirectScoped
+public class RedirectScopedClass implements Serializable {
+
+	private static final long serialVersionUID = 3676449534190350039L;
+}
diff --git a/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/tests/RedirectScopedTest.java b/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/tests/RedirectScopedTest.java
new file mode 100644
index 0000000..11b9392
--- /dev/null
+++ b/pluto-container/src/test/java/org/apache/pluto/container/bean/processor/tests/RedirectScopedTest.java
@@ -0,0 +1,96 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+
+package org.apache.pluto.container.bean.processor.tests;
+
+import org.apache.pluto.container.bean.processor.AnnotatedConfigBean;
+import org.apache.pluto.container.bean.processor.AnnotatedMethodStore;
+import org.apache.pluto.container.bean.processor.ConfigSummary;
+import org.apache.pluto.container.bean.processor.PortletCDIExtension;
+import org.apache.pluto.container.bean.processor.RedirectScopedConfig;
+import org.apache.pluto.container.bean.processor.fixtures.RedirectScopedClass;
+import org.jglue.cdiunit.AdditionalClasses;
+import org.jglue.cdiunit.AdditionalPackages;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for RedirectScoped beans
+ * 
+ * @author Scott Nicklous
+ * @author Neil Griffin
+ *
+ */
+@RunWith(CdiRunner.class)
+@AdditionalClasses(PortletCDIExtension.class)
+@AdditionalPackages(RedirectScopedClass.class)
+public class RedirectScopedTest {
+   
+   @Inject
+   AnnotatedConfigBean acb;
+   
+   @Inject
+   BeanManager beanmgr;
+   
+   private AnnotatedMethodStore annotatedMethodStore;
+   private ConfigSummary summary;
+   private RedirectScopedConfig redirectScopedConfig;
+   
+   @Before
+   public void setUp() {
+      annotatedMethodStore = acb.getMethodStore();
+      summary = acb.getSummary();
+      redirectScopedConfig = acb.getRedirectScopedConfig();
+      redirectScopedConfig.activate(beanmgr);
+   }
+
+   @Test
+   public void injectBeanTest() {
+      assertNotNull(acb);
+      assertNotNull(annotatedMethodStore);
+      assertNotNull(summary);
+      assertNotNull(redirectScopedConfig);
+   }
+   
+   @Test
+   public void annotatedClassPresent1() {
+      Set<Class<?>> classes = redirectScopedConfig.getBeanClasses();
+      assertNotNull(classes);
+      assertEquals(1, classes.size());
+   }
+
+   @Test
+   public void annotatedClassPresent2() {
+      Set<Class<?>> classes = redirectScopedConfig.getBeanClasses();
+      assertNotNull(classes);
+      assertTrue(classes.contains(RedirectScopedClass.class));
+   }
+}
diff --git a/pluto-portal-driver-impl/src/main/java/org/apache/pluto/driver/services/container/PortletAsyncContextImpl.java b/pluto-portal-driver-impl/src/main/java/org/apache/pluto/driver/services/container/PortletAsyncContextImpl.java
index 5ab4991..bd71ffe 100644
--- a/pluto-portal-driver-impl/src/main/java/org/apache/pluto/driver/services/container/PortletAsyncContextImpl.java
+++ b/pluto-portal-driver-impl/src/main/java/org/apache/pluto/driver/services/container/PortletAsyncContextImpl.java
@@ -43,6 +43,7 @@ import org.apache.pluto.container.bean.processor.PortletArtifactProducer;
 import org.apache.pluto.container.bean.processor.PortletRequestScopedBeanHolder;
 import org.apache.pluto.container.bean.processor.PortletSessionBeanHolder;
 import org.apache.pluto.container.bean.processor.PortletStateScopedBeanHolder;
+import org.apache.pluto.container.bean.processor.RedirectScopedBeanHolder;
 import org.apache.pluto.container.impl.HttpServletPortletRequestWrapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -71,6 +72,7 @@ public class PortletAsyncContextImpl implements PortletAsyncManager, AsyncContex
    private PortletSessionBeanHolder            sessbh;
    private PortletStateScopedBeanHolder        statebh;
    private PortletRequestScopedBeanHolder      reqbh;
+   private RedirectScopedBeanHolder            redbh;
    private BeanManager                         beanmgr;
    private Runnable                            pendingRunner;
    private boolean                             doDeregister       = true;
@@ -128,6 +130,7 @@ public class PortletAsyncContextImpl implements PortletAsyncManager, AsyncContex
          PortletSessionBeanHolder.register(sessbh);
          PortletStateScopedBeanHolder.register(statebh);
          PortletRequestScopedBeanHolder.register(reqbh);
+         RedirectScopedBeanHolder.register(redbh);
          PortletArtifactProducer.setPrecursors(resreq, prctx.getResponse(), prctx.getPortletConfig());
       }
 
@@ -149,6 +152,7 @@ public class PortletAsyncContextImpl implements PortletAsyncManager, AsyncContex
    public void deregisterContext(boolean isListener) {
       if (!complete && (!isListener || doDeregister)) {
          this.sessbh = PortletSessionBeanHolder.deregister();
+         this.redbh = RedirectScopedBeanHolder.deregister();
          this.statebh = PortletStateScopedBeanHolder.deregister();
          this.reqbh = PortletRequestScopedBeanHolder.deregister();
          PortletArtifactProducer.remove();
diff --git a/pom.xml b/pom.xml
index ab93f30..5523865 100644
--- a/pom.xml
+++ b/pom.xml
@@ -291,24 +291,15 @@ generate mailto links. -->
     <xmlunit.version>1.1</xmlunit.version>
     <cdi.version>2.3.1.Final</cdi.version>
     <annotation-detector.version>3.0.5</annotation-detector.version>
-
-    <!-- The following properties are not directly used as maven
-dependencies, they're used by the maven pluto plugin for
-handling installation dependencies
-TODO: Check if we need all of them. -->
-    <xerces.version>2.6.2</xerces.version>
-    <xalan.version>2.7.0</xalan.version>
-    <castor.version>1.1.1</castor.version>
-    <commons-collections.version>3.2</commons-collections.version>
-    <commons-httpclient.version>3.0</commons-httpclient.version>
-    <commons-beanutils.version>1.7.0</commons-beanutils.version>
-    <log4j.version>1.2.14</log4j.version>
-    <activation.version>1.1</activation.version>
-    <ccpp-ri.version>1.0</ccpp-ri.version>
-    <rdffilter.version>1.0</rdffilter.version>
-    <jena.version>1.4.0</jena.version>
-
-    <!-- scope of certain dependencies is "compile" for deployment on tomcat, 
+    <!-- MVC ================================================== -->
+    <classmate.version>1.3.4</classmate.version>
+    <hibernate-validator.version>6.0.14.Final</hibernate-validator.version>
+    <jaxrs-api.version>2.1.1</jaxrs-api.version>
+    <mvc-api.version>1.0-pfd</mvc-api.version>
+    <jboss-logging.version>3.3.2.Final</jboss-logging.version>
+    <validation-api.version>2.0.1.Final</validation-api.version>
+
+    <!-- scope of certain dependencies is "compile" for deployment on tomcat,
          but "provided" for deployment on tomee. See tomee profile. -->
     <dependency.scope>compile</dependency.scope>
     
@@ -349,15 +340,15 @@ TODO: Check if we need all of them. -->
 
       <!-- Specification Libraries =========================================== -->
       <dependency>
-        <groupId>javax.inject</groupId>
-        <artifactId>javax.inject</artifactId>
-        <version>1</version>
+        <groupId>javax.annotation</groupId>
+        <artifactId>javax.annotation-api</artifactId>
+        <version>1.3.1</version>
         <scope>provided</scope>
       </dependency>
       <dependency>
-        <groupId>javax.mvc</groupId>
-        <artifactId>javax.mvc-api</artifactId>
-        <version>1.0-pfd</version>
+        <groupId>javax.inject</groupId>
+        <artifactId>javax.inject</artifactId>
+        <version>1</version>
         <scope>provided</scope>
       </dependency>
       <dependency>
@@ -373,12 +364,6 @@ TODO: Check if we need all of them. -->
         <scope>provided</scope>
       </dependency>
       <dependency>
-        <groupId>javax.ws.rs</groupId>
-        <artifactId>javax.ws.rs-api</artifactId>
-        <version>2.1.1</version>
-        <scope>provided</scope>
-      </dependency>
-      <dependency>
         <groupId>org.apache.tomcat</groupId>
         <artifactId>tomcat-servlet-api</artifactId>
         <version>${tomcat.servlet-api.version}</version>
@@ -459,6 +444,51 @@ TODO: Check if we need all of them. -->
         <version>${xmlunit.version}</version>
         <scope>test</scope>
       </dependency>
+      <!-- MVC ================================================== -->
+      <dependency>
+        <groupId>com.fasterxml</groupId>
+        <artifactId>classmate</artifactId>
+        <version>${classmate.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.hibernate.validator</groupId>
+        <artifactId>hibernate-validator</artifactId>
+        <version>${hibernate-validator.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>org.hibernate.validator</groupId>
+        <artifactId>hibernate-validator-cdi</artifactId>
+        <version>${hibernate-validator.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.jboss.logging</groupId>
+        <artifactId>jboss-logging</artifactId>
+        <version>${jboss-logging.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>javax.mvc</groupId>
+        <artifactId>javax.mvc-api</artifactId>
+        <version>${mvc-api.version}</version>
+        <scope>provided</scope>
+      </dependency>
+      <dependency>
+        <groupId>javax.validation</groupId>
+        <artifactId>validation-api</artifactId>
+        <version>${validation-api.version}</version>
+        <scope>provided</scope>
+      </dependency>
+      <dependency>
+        <groupId>javax.ws.rs</groupId>
+        <artifactId>javax.ws.rs-api</artifactId>
+        <version>${jaxrs-api.version}</version>
+        <scope>provided</scope>
+      </dependency>
       <!-- Misc ================================================= -->
       <dependency>
         <groupId>ant</groupId>
@@ -521,17 +551,6 @@ TODO: Check if we need all of them. -->
         <version>${taglibs.standard.version}</version>
       </dependency>
       <dependency>
-        <groupId>org.hibernate.validator</groupId>
-        <artifactId>hibernate-validator</artifactId>
-        <version>6.0.14.Final</version>
-        <exclusions>
-          <exclusion>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-api</artifactId>
          <version>3.11.0</version>
@@ -604,6 +623,7 @@ TODO: Check if we need all of them. -->
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>${commons-lang.version}</version>
+        <scope>provided</scope>
       </dependency>
       <dependency>
         <groupId>commons-digester</groupId>
diff --git a/portlet-tck_3.0/V2AddlRequestTests/pom.xml b/portlet-tck_3.0/V2AddlRequestTests/pom.xml
index afdf932..21cea09 100644
--- a/portlet-tck_3.0/V2AddlRequestTests/pom.xml
+++ b/portlet-tck_3.0/V2AddlRequestTests/pom.xml
@@ -47,7 +47,6 @@
       <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
-         <version>3.0</version>
       </dependency>
  
       <!-- for tooling purposes -->
diff --git a/portlet-tck_3.0/V3HeaderPortletTests/pom.xml b/portlet-tck_3.0/V3HeaderPortletTests/pom.xml
index b1c6537..d957173 100644
--- a/portlet-tck_3.0/V3HeaderPortletTests/pom.xml
+++ b/portlet-tck_3.0/V3HeaderPortletTests/pom.xml
@@ -46,7 +46,6 @@
       <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
-         <version>3.0</version>
       </dependency>
 
       <!-- for tooling purposes -->