Moves plugins

git-svn-id: https://svn.apache.org/repos/asf/struts/archive/trunk@1672653 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/plugins/struts2-portlet-plugin/pom.xml b/plugins/struts2-portlet-plugin/pom.xml
new file mode 100644
index 0000000..b9435cf
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/pom.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.struts</groupId>
+        <artifactId>struts2-plugins</artifactId>
+        <version>2.3.1-SNAPSHOT</version>
+    </parent>
+    <groupId>org.apache.struts</groupId>
+    <artifactId>struts2-portlet-plugin</artifactId>
+    <packaging>jar</packaging>
+    <name>Struts 2 Portlet Plugin</name>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/struts/struts2/trunk/plugins/portlet/</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/struts/struts2/trunk/plugins/portlet/</developerConnection>
+        <url>http://svn.apache.org/viewcvs.cgi/struts/struts2/trunk/plugins/portlet/</url>
+    </scm>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>struts2-junit-plugin</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jsp-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+
+        <!-- Velocity -->
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-tools</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Portlet -->
+        <dependency>
+            <groupId>portlet-api</groupId>
+            <artifactId>portlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>mockobjects</groupId>
+            <artifactId>mockobjects-jdk1.3-j2ee1.3</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jmock</groupId>
+            <artifactId>jmock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>jmock</groupId>
+            <artifactId>jmock-cglib</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>mockobjects</groupId>
+            <artifactId>mockobjects-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Mocks for unit testing (by Spring) -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <version>2.5.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc-portlet</artifactId>
+            <version>2.5.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>2.5.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/components/PortletUrlRenderer.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/components/PortletUrlRenderer.java
new file mode 100644
index 0000000..1ffb9ec
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/components/PortletUrlRenderer.java
@@ -0,0 +1,186 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.components;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.commons.lang.StringUtils;
+import org.apache.struts2.StrutsException;
+import org.apache.struts2.dispatcher.mapper.ActionMapper;
+import org.apache.struts2.portlet.context.PortletActionContext;
+import org.apache.struts2.portlet.util.PortletUrlHelper;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Implementation of the {@link UrlRenderer} interface that renders URLs for portlet environments.
+ * 
+ * @see UrlRenderer
+ *
+ */
+public class PortletUrlRenderer implements UrlRenderer {
+	
+	/**
+	 * The servlet renderer used when not executing in a portlet context.
+	 */
+	private UrlRenderer servletRenderer = null;
+	
+	public PortletUrlRenderer() {
+		this.servletRenderer = new ServletUrlRenderer();
+	}
+
+	@Inject
+	public void setActionMapper(ActionMapper actionMapper) {
+		servletRenderer.setActionMapper(actionMapper);
+	}
+	
+	/**
+	 * {@inheritDoc}
+	 */
+	public void renderUrl(Writer writer, UrlProvider urlComponent) {
+		if(PortletActionContext.getPortletContext() == null || "none".equalsIgnoreCase(urlComponent.getPortletUrlType())) {
+			servletRenderer.renderUrl(writer, urlComponent);
+		}
+		else {
+			String action = null;
+			if(urlComponent.getAction() != null) {
+				action = urlComponent.findString(urlComponent.getAction());
+			}
+			String scheme = urlComponent.getHttpServletRequest().getScheme();
+
+			if (urlComponent.getScheme() != null) {
+				scheme = urlComponent.getScheme();
+			}
+
+			String result;
+			urlComponent.setNamespace(urlComponent.determineNamespace(urlComponent.getNamespace(), urlComponent.getStack(), urlComponent.getHttpServletRequest()));
+			if (onlyActionSpecified(urlComponent)) {
+				result = PortletUrlHelper.buildUrl(action, urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getParameters(), urlComponent.getPortletUrlType(),
+                        urlComponent.getPortletMode(), urlComponent.getWindowState());
+			} else if(onlyValueSpecified(urlComponent)){
+				result = PortletUrlHelper.buildResourceUrl(urlComponent.getValue(), urlComponent.getParameters());
+			}
+			else {
+				result = createDefaultUrl(urlComponent);
+			}
+            String anchor = urlComponent.getAnchor();
+			if (StringUtils.isNotEmpty(anchor)) {
+				result += '#' + urlComponent.findString(anchor);
+			}
+
+			String var = urlComponent.getVar();
+
+			if (var != null) {
+				urlComponent.putInContext(result);
+
+				// add to the request and page scopes as well
+				urlComponent.getHttpServletRequest().setAttribute(var, result);
+			} else {
+				try {
+					writer.write(result);
+				} catch (IOException e) {
+					throw new StrutsException("IOError: " + e.getMessage(), e);
+				}
+			}
+		}
+	}
+
+	private String createDefaultUrl(UrlProvider urlComponent) {
+		String result;
+		ActionInvocation ai = (ActionInvocation)urlComponent.getStack().getContext().get(
+				ActionContext.ACTION_INVOCATION);
+		String action = ai.getProxy().getActionName();
+		result = PortletUrlHelper.buildUrl(action, urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getParameters(),
+                urlComponent.getPortletUrlType(), urlComponent.getPortletMode(), urlComponent.getWindowState());
+		return result;
+	}
+
+	private boolean onlyValueSpecified(UrlProvider urlComponent) {
+		return urlComponent.getValue() != null && urlComponent.getAction() == null;
+	}
+
+	private boolean onlyActionSpecified(UrlProvider urlComponent) {
+		return urlComponent.getValue() == null && urlComponent.getAction() != null;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void renderFormUrl(Form formComponent) {
+		if(PortletActionContext.getPortletContext() == null) {
+			servletRenderer.renderFormUrl(formComponent);
+		}
+		else {
+			String namespace = formComponent.determineNamespace(formComponent.namespace, formComponent.getStack(),
+					formComponent.request);
+			String action = null;
+			if (formComponent.action != null) {
+				action = formComponent.findString(formComponent.action);
+			}
+			else {
+				ActionInvocation ai = (ActionInvocation) formComponent.getStack().getContext().get(ActionContext.ACTION_INVOCATION);
+				action = ai.getProxy().getActionName();
+			}
+			String type = "action";
+			if (StringUtils.isNotEmpty(formComponent.method)) {
+				if ("GET".equalsIgnoreCase(formComponent.method.trim())) {
+					type = "render";
+				}
+			}
+			if (action != null) {
+				String result = PortletUrlHelper.buildUrl(action, namespace, null,
+						formComponent.getParameters(), type, formComponent.portletMode, formComponent.windowState);
+				formComponent.addParameter("action", result);
+
+
+				// name/id: cut out anything between / and . should be the id and
+				// name
+				String id = formComponent.getId();
+				if (id == null) {
+					int slash = action.lastIndexOf('/');
+					int dot = action.indexOf('.', slash);
+					if (dot != -1) {
+						id = action.substring(slash + 1, dot);
+					} else {
+						id = action.substring(slash + 1);
+					}
+					formComponent.addParameter("id", formComponent.escape(id));
+				}
+			}
+		}
+		
+	}
+
+	public void beforeRenderUrl(UrlProvider urlComponent) {
+		if(PortletActionContext.getPortletContext() == null) {
+			servletRenderer.beforeRenderUrl(urlComponent);
+		}
+	}
+
+	public void setServletRenderer(UrlRenderer nonPortletRenderer) {
+		this.servletRenderer = nonPortletRenderer;
+		
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletActionConstants.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletActionConstants.java
new file mode 100644
index 0000000..be2daff
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletActionConstants.java
@@ -0,0 +1,140 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.portlet.dispatcher.DispatcherServlet;
+
+/**
+ * Interface defining some constants used in the Struts portlet implementation
+ *
+ */
+public interface PortletActionConstants {
+    /**
+     * Default action name to use when no default action has been configured in the portlet
+     * init parameters.
+     */
+    String DEFAULT_ACTION_NAME = "default";
+
+    /**
+     * Action name parameter name
+     */
+    String ACTION_PARAM = "struts.portlet.action";
+
+    /**
+     * Key for parameter holding the last executed portlet mode.
+     */
+    String MODE_PARAM = "struts.portlet.mode";
+
+    /**
+     * Key used for looking up and storing the portlet phase
+     */
+    String PHASE = "struts.portlet.phase";
+
+    /**
+     * Constant used for the render phase (
+     * {@link javax.portlet.Portlet#render(javax.portlet.RenderRequest, javax.portlet.RenderResponse)})
+     */
+    Integer RENDER_PHASE = new Integer(1);
+
+    /**
+     * Constant used for the event phase (
+     * {@link javax.portlet.Portlet#processAction(javax.portlet.ActionRequest, javax.portlet.ActionResponse)})
+     */
+    Integer EVENT_PHASE = new Integer(2);
+
+    /**
+     * Key used for looking up and storing the
+     * {@link javax.portlet.PortletRequest}
+     */
+    String REQUEST = "struts.portlet.request";
+
+    /**
+     * Key used for looking up and storing the
+     * {@link javax.portlet.PortletResponse}
+     */
+    String RESPONSE = "struts.portlet.response";
+
+    /**
+     * Key used for looking up and storing the action that was invoked in the event phase.
+     */
+    String EVENT_ACTION = "struts.portlet.eventAction";
+
+    /**
+     * Key used for looking up and storing the
+     * {@link javax.portlet.PortletConfig}
+     */
+    String PORTLET_CONFIG = "struts.portlet.config";
+
+    /**
+     * Name of the action used as error handler
+     */
+    String ERROR_ACTION = "errorHandler";
+
+    /**
+     * Key for the portlet namespace stored in the
+     * {@link org.apache.struts2.portlet.context.PortletActionContext}.
+     */
+    String PORTLET_NAMESPACE = "struts.portlet.portletNamespace";
+
+    /**
+     * Key for the mode-to-namespace map stored in the
+     * {@link org.apache.struts2.portlet.context.PortletActionContext}.
+     */
+    String MODE_NAMESPACE_MAP = "struts.portlet.modeNamespaceMap";
+
+    /**
+     * Key for the default action name for the portlet, stored in the
+     * {@link org.apache.struts2.portlet.context.PortletActionContext}.
+     */
+    String DEFAULT_ACTION_FOR_MODE = "struts.portlet.defaultActionForMode";
+    
+    /**
+     * Key for request attribute indicating if the action has been reset. 
+     */
+    String ACTION_RESET = "struts.portlet.actionReset";
+    
+    /**
+     * Key for session attribute indicating the location of the render direct action.
+     */
+    String RENDER_DIRECT_LOCATION = "struts.portlet.renderDirectLocation";
+    
+    /**
+     * Key for the dispatch instruction for the {@link DispatcherServlet}
+     */
+	String DISPATCH_TO = "struts.portlet.dispatchTo";
+	
+	/**
+	 * Session key where the value stack from the event phase is stored.
+	 */
+	String STACK_FROM_EVENT_PHASE = "struts.portlet.valueStackFromEventPhase";
+
+	/**
+	 * Default name of dispatcher servlet in web.xml
+	 */
+	String DEFAULT_DISPATCHER_SERVLET_NAME = "Struts2PortletDispatcherServlet";
+	
+	/**
+	 * Key for the action mapping in the context
+    */
+	String ACTION_MAPPING = ServletActionContext.ACTION_MAPPING;
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletApplicationMap.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletApplicationMap.java
new file mode 100644
index 0000000..436c2ae
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletApplicationMap.java
@@ -0,0 +1,207 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.io.Serializable;
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortletContext;
+
+/**
+ * Portlet specific {@link java.util.Map} implementation representing the
+ * {@link javax.portlet.PortletContext} of a Portlet.
+ *
+ */
+public class PortletApplicationMap extends AbstractMap implements Serializable {
+
+    private static final long serialVersionUID = 2296107511063504414L;
+
+    private PortletContext context;
+
+    private Set<Object> entries;
+
+    /**
+     * Creates a new map object given the {@link PortletContext}.
+     *
+     * @param ctx The portlet context.
+     */
+    public PortletApplicationMap(PortletContext ctx) {
+        this.context = ctx;
+    }
+
+    /**
+     * Removes all entries from the Map and removes all attributes from the
+     * portlet context.
+     */
+    public void clear() {
+        entries = null;
+
+        Enumeration e = context.getAttributeNames();
+
+        while (e.hasMoreElements()) {
+            context.removeAttribute(e.nextElement().toString());
+        }
+    }
+
+    /**
+     * Creates a Set of all portlet context attributes as well as context init
+     * parameters.
+     *
+     * @return a Set of all portlet context attributes as well as context init
+     *         parameters.
+     */
+    public Set entrySet() {
+        if (entries == null) {
+            entries = new HashSet<Object>();
+
+            // Add portlet context attributes
+            Enumeration enumeration = context.getAttributeNames();
+
+            while (enumeration.hasMoreElements()) {
+                final String key = enumeration.nextElement().toString();
+                final Object value = context.getAttribute(key);
+                entries.add(new Map.Entry() {
+                    public boolean equals(Object obj) {
+                        Map.Entry entry = (Map.Entry) obj;
+
+                        return ((key == null) ? (entry.getKey() == null) : key
+                                .equals(entry.getKey()))
+                                && ((value == null) ? (entry.getValue() == null)
+                                        : value.equals(entry.getValue()));
+                    }
+
+                    public int hashCode() {
+                        return ((key == null) ? 0 : key.hashCode())
+                                ^ ((value == null) ? 0 : value.hashCode());
+                    }
+
+                    public Object getKey() {
+                        return key;
+                    }
+
+                    public Object getValue() {
+                        return value;
+                    }
+
+                    public Object setValue(Object obj) {
+                        context.setAttribute(key.toString(), obj);
+
+                        return value;
+                    }
+                });
+            }
+
+            // Add portlet context init params
+            enumeration = context.getInitParameterNames();
+
+            while (enumeration.hasMoreElements()) {
+                final String key = enumeration.nextElement().toString();
+                final Object value = context.getInitParameter(key);
+                entries.add(new Map.Entry() {
+                    public boolean equals(Object obj) {
+                        Map.Entry entry = (Map.Entry) obj;
+
+                        return ((key == null) ? (entry.getKey() == null) : key
+                                .equals(entry.getKey()))
+                                && ((value == null) ? (entry.getValue() == null)
+                                        : value.equals(entry.getValue()));
+                    }
+
+                    public int hashCode() {
+                        return ((key == null) ? 0 : key.hashCode())
+                                ^ ((value == null) ? 0 : value.hashCode());
+                    }
+
+                    public Object getKey() {
+                        return key;
+                    }
+
+                    public Object getValue() {
+                        return value;
+                    }
+
+                    public Object setValue(Object obj) {
+                        context.setAttribute(key.toString(), obj);
+
+                        return value;
+                    }
+                });
+            }
+        }
+
+        return entries;
+    }
+
+    /**
+     * Returns the portlet context attribute or init parameter based on the
+     * given key. If the entry is not found, <tt>null</tt> is returned.
+     *
+     * @param key
+     *            the entry key.
+     * @return the portlet context attribute or init parameter or <tt>null</tt>
+     *         if the entry is not found.
+     */
+    public Object get(Object key) {
+        // Try context attributes first, then init params
+        // This gives the proper shadowing effects
+        String keyString = key.toString();
+        Object value = context.getAttribute(keyString);
+
+        return (value == null) ? context.getInitParameter(keyString) : value;
+    }
+
+    /**
+     * Sets a portlet context attribute given a attribute name and value.
+     *
+     * @param key
+     *            the name of the attribute.
+     * @param value
+     *            the value to set.
+     * @return the attribute that was just set.
+     */
+    public Object put(Object key, Object value) {
+        entries = null;
+        context.setAttribute(key.toString(), value);
+
+        return get(key);
+    }
+
+    /**
+     * Removes the specified portlet context attribute.
+     *
+     * @param key
+     *            the attribute to remove.
+     * @return the entry that was just removed.
+     */
+    public Object remove(Object key) {
+        entries = null;
+
+        Object value = get(key);
+        context.removeAttribute(key.toString());
+
+        return value;
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java
new file mode 100644
index 0000000..1ff6acb
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java
@@ -0,0 +1,158 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.portlet.PortletRequest;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * A simple implementation of the {@link java.util.Map} interface to handle a collection of request attributes.
+ *
+ */
+public class PortletRequestMap extends AbstractMap {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortletRequestMap.class);
+
+    private Set<Object> entries = null;
+    private PortletRequest request = null;
+
+    /**
+     * Saves the request to use as the backing for getting and setting values
+     *
+     * @param request the portlet request.
+     */
+    public PortletRequestMap(PortletRequest request) {
+        this.request = request;
+    }
+
+    /**
+     * Removes all attributes from the request as well as clears entries in this
+     * map.
+     */
+    public void clear() {
+        entries = null;
+        Enumeration keys = request.getAttributeNames();
+
+        while (keys.hasMoreElements()) {
+            String key = (String) keys.nextElement();
+            request.removeAttribute(key);
+        }
+    }
+
+    /**
+     * Returns a Set of attributes from the portlet request.
+     *
+     * @return a Set of attributes from the portlet request.
+     */
+    public Set entrySet() {
+        if (entries == null) {
+            entries = new HashSet<Object>();
+
+            Enumeration enumeration = request.getAttributeNames();
+
+            while (enumeration.hasMoreElements()) {
+                final String key = enumeration.nextElement().toString();
+                final Object value = request.getAttribute(key);
+                entries.add(new Entry() {
+                    public boolean equals(Object obj) {
+                        Entry entry = (Entry) obj;
+
+                        return ((key == null) ? (entry.getKey() == null) : key
+                                .equals(entry.getKey()))
+                                && ((value == null) ? (entry.getValue() == null)
+                                        : value.equals(entry.getValue()));
+                    }
+
+                    public int hashCode() {
+                        return ((key == null) ? 0 : key.hashCode())
+                                ^ ((value == null) ? 0 : value.hashCode());
+                    }
+
+                    public Object getKey() {
+                        return key;
+                    }
+
+                    public Object getValue() {
+                        return value;
+                    }
+
+                    public Object setValue(Object obj) {
+                        request.setAttribute(key, obj);
+
+                        return value;
+                    }
+                });
+            }
+        }
+
+        return entries;
+    }
+
+    /**
+     * Returns the request attribute associated with the given key or
+     * <tt>null</tt> if it doesn't exist.
+     *
+     * @param key the name of the request attribute.
+     * @return the request attribute or <tt>null</tt> if it doesn't exist.
+     */
+    public Object get(Object key) {
+        return request.getAttribute(key.toString());
+    }
+
+    /**
+     * Saves an attribute in the request.
+     *
+     * @param key the name of the request attribute.
+     * @param value the value to set.
+     * @return the object that was just set.
+     */
+    public Object put(Object key, Object value) {
+        entries = null;
+        request.setAttribute(key.toString(), value);
+
+        return get(key);
+    }
+
+    /**
+     * Removes the specified request attribute.
+     *
+     * @param key the name of the attribute to remove.
+     * @return the value that was removed or <tt>null</tt> if the value was
+     * not found (and hence, not removed).
+     */
+    public Object remove(Object key) {
+        entries = null;
+
+        Object value = get(key);
+        request.removeAttribute(key.toString());
+
+        return value;
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletSessionMap.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletSessionMap.java
new file mode 100644
index 0000000..c11d7fe
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/PortletSessionMap.java
@@ -0,0 +1,163 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * A simple implementation of the {@link java.util.Map} interface to handle a collection of portlet session
+ * attributes. The {@link #entrySet()} method enumerates over all session attributes and creates a Set of entries.
+ * Note, this will occur lazily - only when the entry set is asked for.
+ *
+ */
+public class PortletSessionMap extends AbstractMap {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortletSessionMap.class);
+
+    private PortletSession session = null;
+    private Set<Object> entries = null;
+
+    /**
+     * Creates a new session map given a portlet request.
+     *
+     * @param request the portlet request object.
+     */
+    public PortletSessionMap(PortletRequest request) {
+        this.session = request.getPortletSession();
+    }
+
+    /**
+     * @see java.util.Map#entrySet()
+     */
+    public Set entrySet() {
+        synchronized (session) {
+            if (entries == null) {
+                entries = new HashSet<Object>();
+
+                Enumeration enumeration = session.getAttributeNames();
+
+                while (enumeration.hasMoreElements()) {
+                    final String key = enumeration.nextElement().toString();
+                    final Object value = session.getAttribute(key);
+                    entries.add(new Map.Entry() {
+                        public boolean equals(Object obj) {
+                            Map.Entry entry = (Map.Entry) obj;
+
+                            return ((key == null) ? (entry.getKey() == null)
+                                    : key.equals(entry.getKey()))
+                                    && ((value == null) ? (entry.getValue() == null)
+                                            : value.equals(entry.getValue()));
+                        }
+
+                        public int hashCode() {
+                            return ((key == null) ? 0 : key.hashCode())
+                                    ^ ((value == null) ? 0 : value.hashCode());
+                        }
+
+                        public Object getKey() {
+                            return key;
+                        }
+
+                        public Object getValue() {
+                            return value;
+                        }
+
+                        public Object setValue(Object obj) {
+                            session.setAttribute(key, obj);
+
+                            return value;
+                        }
+                    });
+                }
+            }
+        }
+
+        return entries;
+    }
+
+    /**
+     * Returns the session attribute associated with the given key or
+     * <tt>null</tt> if it doesn't exist.
+     *
+     * @param key the name of the session attribute.
+     * @return the session attribute or <tt>null</tt> if it doesn't exist.
+     */
+    public Object get(Object key) {
+        synchronized (session) {
+            return session.getAttribute(key.toString());
+        }
+    }
+
+    /**
+     * Saves an attribute in the session.
+     *
+     * @param key the name of the session attribute.
+     * @param value the value to set.
+     * @return the object that was just set.
+     */
+    public Object put(Object key, Object value) {
+        synchronized (session) {
+            entries = null;
+            session.setAttribute(key.toString(), value);
+
+            return get(key);
+        }
+    }
+
+    /**
+     * @see java.util.Map#clear()
+     */
+    public void clear() {
+        synchronized (session) {
+            entries = null;
+            session.invalidate();
+        }
+    }
+
+    /**
+     * Removes the specified session attribute.
+     *
+     * @param key the name of the attribute to remove.
+     * @return the value that was removed or <tt>null</tt> if the value was
+     * not found (and hence, not removed).
+     */
+    public Object remove(Object key) {
+        synchronized (session) {
+            entries = null;
+
+            Object value = get(key);
+            session.removeAttribute(key.toString());
+
+            return value;
+        }
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PortletActionContext.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PortletActionContext.java
new file mode 100644
index 0000000..edb9e31
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PortletActionContext.java
@@ -0,0 +1,241 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.context;
+
+import java.util.Map;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.portlet.PortletActionConstants;
+
+import com.opensymphony.xwork2.ActionContext;
+
+
+/**
+ * PortletActionContext. ActionContext thread local for the portlet environment.
+ *
+ * @version $Revision$ $Date$
+ */
+public class PortletActionContext implements PortletActionConstants {
+
+    /**
+     * Get the PortletConfig of the portlet that is executing.
+     *
+     * @return The PortletConfig of the executing portlet.
+     */
+    public static PortletConfig getPortletConfig() {
+        return (PortletConfig) getContext().get(PORTLET_CONFIG);
+    }
+
+    /**
+     * Get the RenderRequest. Can only be invoked in the render phase.
+     *
+     * @return The current RenderRequest.
+     * @throws IllegalStateException If the method is invoked in the wrong phase.
+     */
+    public static RenderRequest getRenderRequest() {
+        if (!isRender()) {
+            throw new IllegalStateException(
+                    "RenderRequest cannot be obtained in event phase");
+        }
+        return (RenderRequest) getContext().get(REQUEST);
+    }
+
+    /**
+     * Get the RenderResponse. Can only be invoked in the render phase.
+     *
+     * @return The current RenderResponse.
+     * @throws IllegalStateException If the method is invoked in the wrong phase.
+     */
+    public static RenderResponse getRenderResponse() {
+        if (!isRender()) {
+            throw new IllegalStateException(
+                    "RenderResponse cannot be obtained in event phase");
+        }
+        return (RenderResponse) getContext().get(RESPONSE);
+    }
+
+    /**
+     * Get the ActionRequest. Can only be invoked in the event phase.
+     *
+     * @return The current ActionRequest.
+     * @throws IllegalStateException If the method is invoked in the wrong phase.
+     */
+    public static ActionRequest getActionRequest() {
+        if (!isEvent()) {
+            throw new IllegalStateException(
+                    "ActionRequest cannot be obtained in render phase");
+        }
+        return (ActionRequest) getContext().get(REQUEST);
+    }
+
+    /**
+     * Get the ActionRequest. Can only be invoked in the event phase.
+     *
+     * @return The current ActionRequest.
+     * @throws IllegalStateException If the method is invoked in the wrong phase.
+     */
+    public static ActionResponse getActionResponse() {
+        if (!isEvent()) {
+            throw new IllegalStateException(
+                    "ActionResponse cannot be obtained in render phase");
+        }
+        return (ActionResponse) getContext().get(RESPONSE);
+    }
+
+    /**
+     * Get the action namespace of the portlet. Used to organize actions for multiple portlets in
+     * the same portlet application.
+     *
+     * @return The portlet namespace as defined in <code>portlet.xml</code> and <code>struts.xml</code>
+     */
+    public static String getPortletNamespace() {
+        return (String)getContext().get(PORTLET_NAMESPACE);
+    }
+
+    /**
+     * Get the current PortletRequest.
+     *
+     * @return The current PortletRequest.
+     */
+    public static PortletRequest getRequest() {
+        return (PortletRequest) getContext().get(REQUEST);
+    }
+    
+    /**
+     * Convenience setter for the portlet request.
+     * @param request
+     */
+    public static void setRequest(PortletRequest request) {
+    	getContext().put(REQUEST, request);
+    }
+
+    /**
+     * Get the current PortletResponse
+     *
+     * @return The current PortletResponse.
+     */
+    public static PortletResponse getResponse() {
+        return (PortletResponse) getContext().get(RESPONSE);
+    }
+    
+    /**
+     * Convenience setter for the portlet response.
+     * @param response
+     */
+    public static void setResponse(PortletResponse response) {
+    	getContext().put(RESPONSE, response);
+    }
+
+    /**
+     * Get the phase that the portlet is executing in.
+     *
+     * @return {@link PortletActionConstants#RENDER_PHASE} in render phase, and
+     * {@link PortletActionConstants#EVENT_PHASE} in the event phase.
+     */
+    public static Integer getPhase() {
+        return (Integer) getContext().get(PHASE);
+    }
+
+    /**
+     * @return <code>true</code> if the Portlet is executing in render phase.
+     */
+    public static boolean isRender() {
+        return PortletActionConstants.RENDER_PHASE.equals(getPhase());
+    }
+
+    /**
+     * @return <code>true</code> if the Portlet is executing in the event phase.
+     */
+    public static boolean isEvent() {
+        return PortletActionConstants.EVENT_PHASE.equals(getPhase());
+    }
+
+    /**
+     * @return The current ActionContext.
+     */
+    private static ActionContext getContext() {
+        return ActionContext.getContext();
+    }
+
+    /**
+     * Check to see if the current request is a portlet request.
+     *
+     * @return <code>true</code> if the current request is a portlet request.
+     */
+    public static boolean isPortletRequest() {
+        return getRequest() != null;
+    }
+
+    /**
+     * Get the default action mapping for the current mode.
+     *
+     * @return The default action mapping for the current portlet mode.
+     */
+    public static ActionMapping getDefaultActionForMode() {
+        return (ActionMapping)getContext().get(DEFAULT_ACTION_FOR_MODE);
+    }
+
+    /**
+     * Get the namespace to mode mappings.
+     *
+     * @return The map of the namespaces for each mode.
+     */
+    public static Map getModeNamespaceMap() {
+        return (Map)getContext().get(MODE_NAMESPACE_MAP);
+    }
+    
+    /**
+     * Get the portlet context.
+     * @return The portlet context.
+     */
+    public static PortletContext getPortletContext() {
+    	return (PortletContext)getContext().get(StrutsStatics.STRUTS_PORTLET_CONTEXT);
+    }
+    
+    /**
+     * Convenience setter for the portlet context.
+     * @param context
+     */
+    public static void setPortletContext(PortletContext context) {
+    	getContext().put(StrutsStatics.STRUTS_PORTLET_CONTEXT, context);
+    }
+    
+    /**
+     * Gets the action mapping for this context
+     *
+     * @return The action mapping
+     */
+    public static ActionMapping getActionMapping() {
+        return (ActionMapping) getContext().get(ACTION_MAPPING);
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PreparatorServlet.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PreparatorServlet.java
new file mode 100644
index 0000000..a181ef6
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/PreparatorServlet.java
@@ -0,0 +1,52 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.context;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.struts2.StrutsStatics;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * @deprecated
+ * 
+ * This servlet has been deprecated. Do not use it (WW-2101)
+ *
+ */
+public class PreparatorServlet extends HttpServlet implements StrutsStatics {
+
+    private static final long serialVersionUID = 1853399729352984089L;
+
+    private final static Logger LOG = LoggerFactory.getLogger(PreparatorServlet.class);
+
+	@Override
+	public void init(ServletConfig config) throws ServletException {
+            if (LOG.isWarnEnabled()) {
+		LOG.warn("The preparator servlet has been deprecated. It can safely be removed from your web.xml file");
+            }
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/ServletContextHolderListener.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/ServletContextHolderListener.java
new file mode 100644
index 0000000..4ab4275
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/context/ServletContextHolderListener.java
@@ -0,0 +1,69 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.context;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * @deprecated
+ * 
+ * This listener has been deprecated. Do not use it. (WW-2101)
+ *
+ */
+public class ServletContextHolderListener implements ServletContextListener {
+
+    private static ServletContext context = null;
+    
+    private final static Logger LOG = LoggerFactory.getLogger(ServletContextHolderListener.class);
+
+    /**
+     * @return The current servlet context
+     */
+    public static ServletContext getServletContext() {
+        return context;
+    }
+
+    /**
+     * Stores the reference to the {@link ServletContext}.
+     *
+     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+     */
+    public void contextInitialized(ServletContextEvent event) {
+        if (LOG.isWarnEnabled()) {
+    		LOG.warn("The ServletContextHolderListener has been deprecated. It can safely be removed from your web.xml file");
+        }
+        context = event.getServletContext();
+    }
+
+    /**
+     * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+     */
+    public void contextDestroyed(ServletContextEvent event) {
+        context = null;
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DirectRenderFromEventAction.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DirectRenderFromEventAction.java
new file mode 100644
index 0000000..9e2ac6c
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DirectRenderFromEventAction.java
@@ -0,0 +1,73 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.dispatcher;
+
+import com.opensymphony.xwork2.Action;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.apache.struts2.interceptor.SessionAware;
+import org.apache.struts2.portlet.PortletActionConstants;
+
+/**
+ * When a portlet is targetted for an <code>event</code>, the portlet will receive two
+ * portlet requests, one for the <code>event</code> phase, and then followed by a <code>render</code>
+ * operation. When in the <code>event</code> phase, the action that is executed can't render
+ * any output. This means that if an action in the XWork configuration is executed in the event
+ * phase, and the action is set up with a result that should render something, the result can't
+ * immediately be executed. The portlet needs to "wait" to the render phase to do the
+ * rendering.
+ * <p/>
+ * When the {@link org.apache.struts2.portlet.result.PortletResult} detects such a
+ * scenario, instead of executing the actual view, it prepares a couple of render parameters
+ * specifying this action and the location of the view, which then will be executed in the
+ * following render request.
+ */
+public class DirectRenderFromEventAction implements SessionAware, PortletActionConstants, Action, Serializable {
+
+    private static final long serialVersionUID = -1814807772308405785L;
+
+    private String location = null;
+
+    /**
+     * Get the location of the view.
+     *
+     * @return Returns the location.
+     */
+    public String getLocation() {
+        return location;
+    }
+
+    /**
+     * Always return success.
+     *
+     * @return SUCCESS
+     */
+    public String execute() throws Exception {
+        return SUCCESS;
+    }
+
+	public void setSession(Map session) {
+		location = (String)session.get(RENDER_DIRECT_LOCATION);
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DispatcherServlet.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DispatcherServlet.java
new file mode 100644
index 0000000..29a77e5
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/DispatcherServlet.java
@@ -0,0 +1,56 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.dispatcher;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.struts2.dispatcher.StrutsRequestWrapper;
+import org.apache.struts2.portlet.PortletActionConstants;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class DispatcherServlet extends HttpServlet implements PortletActionConstants {
+
+	private static final long serialVersionUID = -266147033645951967L;
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+		String dispatchTo = (String) request.getAttribute(DISPATCH_TO);
+		HttpServletRequest wrapper = wrapRequestIfNecessary(request);
+		if(StringUtils.isNotEmpty(dispatchTo)) {
+			request.getRequestDispatcher(dispatchTo).include(wrapper, response);
+		}
+	}
+
+	private HttpServletRequest wrapRequestIfNecessary(HttpServletRequest request) {
+		if(!(request instanceof StrutsRequestWrapper)) {
+			return new StrutsRequestWrapper(request);
+		}
+		else {
+			return request;
+		}
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
new file mode 100644
index 0000000..1a1ba4a
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
@@ -0,0 +1,628 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.dispatcher;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.GenericPortlet;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletException;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.WindowState;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.StrutsException;
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.dispatcher.ApplicationMap;
+import org.apache.struts2.dispatcher.Dispatcher;
+import org.apache.struts2.dispatcher.RequestMap;
+import org.apache.struts2.dispatcher.SessionMap;
+import org.apache.struts2.dispatcher.mapper.ActionMapper;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.PortletApplicationMap;
+import org.apache.struts2.portlet.PortletRequestMap;
+import org.apache.struts2.portlet.PortletSessionMap;
+import org.apache.struts2.portlet.context.PortletActionContext;
+import org.apache.struts2.portlet.servlet.PortletServletContext;
+import org.apache.struts2.portlet.servlet.PortletServletRequest;
+import org.apache.struts2.portlet.servlet.PortletServletResponse;
+import org.apache.struts2.util.AttributeMap;
+import org.apache.commons.lang.StringUtils;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.ActionProxyFactory;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.FileManager;
+import com.opensymphony.xwork2.util.LocalizedTextUtil;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * <p>
+ * Struts JSR-168 portlet dispatcher. Similar to the WW2 Servlet dispatcher,
+ * but adjusted to a portal environment. The portlet is configured through the <tt>portlet.xml</tt>
+ * descriptor. Examples and descriptions follow below:
+ * </p>
+ * <!-- END SNIPPET: javadoc -->
+ *
+ * @author Nils-Helge Garli
+ * @author Rainer Hermanns
+ *
+ * <p><b>Init parameters</b></p>
+ * <!-- START SNIPPET: params -->
+ * <table class="confluenceTable">
+ * <tr>
+ *  <th class="confluenceTh">Name</th>
+ * <th class="confluenceTh">Description</th>
+ * <th class="confluenceTh">Default value</th>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">portletNamespace</td><td class="confluenceTd">The namespace for the portlet in the xwork configuration. This
+ *      namespace is prepended to all action lookups, and makes it possible to host multiple
+ *      portlets in the same portlet application. If this parameter is set, the complete namespace
+ *      will be <tt>/portletNamespace/modeNamespace/actionName</tt></td><td class="confluenceTd">The default namespace</td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">viewNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>view</tt> portlet
+ *      mode</td><td class="confluenceTd">The default namespace</td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">editNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>edit</tt> portlet
+ *      mode</td><td class="confluenceTd">The default namespace</td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">helpNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>help</tt> portlet
+ *      mode</td><td class="confluenceTd">The default namespace</td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">defaultViewAction</td><td class="confluenceTd">Default action to invoke in the <tt>view</tt> portlet mode if no action is
+ *      specified</td><td class="confluenceTd"><tt>default</tt></td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">defaultEditAction</td><td class="confluenceTd">Default action to invoke in the <tt>edit</tt> portlet mode if no action is
+ *      specified</td><td class="confluenceTd"><tt>default</tt></td>
+ * </tr>
+ * <tr>
+ *  <td class="confluenceTd">defaultHelpAction</td><td class="confluenceTd">Default action to invoke in the <tt>help</tt> portlet mode if no action is
+ *      specified</td><td class="confluenceTd"><tt>default</tt></td>
+ * </tr>
+ * </table>
+ * <!-- END SNIPPET: params -->
+ * <p><b>Example:</b></p>
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ *
+ * &lt;init-param&gt;
+ *     &lt;!-- The view mode namespace. Maps to a namespace in the xwork config file --&gt;
+ *     &lt;name&gt;viewNamespace&lt;/name&gt;
+ *     &lt;value&gt;/view&lt;/value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ *    &lt;!-- The default action to invoke in view mode --&gt;
+ *    &lt;name&gt;defaultViewAction&lt;/name&gt;
+ *    &lt;value&gt;index&lt;/value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ *     &lt;!-- The view mode namespace. Maps to a namespace in the xwork config file --&gt;
+ *     &lt;name&gt;editNamespace&lt;/name&gt;
+ *     &lt;value&gt;/edit&lt;/value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ *     &lt;!-- The default action to invoke in view mode --&gt;
+ *     &lt;name&gt;defaultEditAction&lt;/name&gt;
+ *     &lt;value&gt;index&lt;/value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ *     &lt;!-- The view mode namespace. Maps to a namespace in the xwork config file --&gt;
+ *     &lt;name&gt;helpNamespace&lt;/name&gt;
+ *     &lt;value&gt;/help&lt;/value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ *     &lt;!-- The default action to invoke in view mode --&gt;
+ *     &lt;name&gt;defaultHelpAction&lt;/name&gt;
+ *     &lt;value&gt;index&lt;/value&gt;
+ * &lt;/init-param&gt;
+ *
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ */
+public class Jsr168Dispatcher extends GenericPortlet implements StrutsStatics,
+        PortletActionConstants {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Jsr168Dispatcher.class);
+
+    private ActionProxyFactory factory = null;
+
+    private Map<PortletMode,String> modeMap = new HashMap<PortletMode,String>(3);
+
+    private Map<PortletMode,ActionMapping> actionMap = new HashMap<PortletMode,ActionMapping>(3);
+
+    private String portletNamespace = null;
+
+    private Dispatcher dispatcherUtils;
+    
+    private ActionMapper actionMapper;
+    
+    private Container container;
+
+    /**
+     * Initialize the portlet with the init parameters from <tt>portlet.xml</tt>
+     */
+    public void init(PortletConfig cfg) throws PortletException {
+        super.init(cfg);
+        if (LOG.isDebugEnabled()) LOG.debug("Initializing portlet " + getPortletName());
+        
+        Map<String,String> params = new HashMap<String,String>();
+        for (Enumeration e = cfg.getInitParameterNames(); e.hasMoreElements(); ) {
+            String name = (String) e.nextElement();
+            String value = cfg.getInitParameter(name);
+            params.put(name, value);
+        }
+        
+        dispatcherUtils = new Dispatcher(new PortletServletContext(cfg.getPortletContext()), params);
+        dispatcherUtils.init();
+        
+        // For testability
+        if (factory == null) {
+            factory = dispatcherUtils.getConfigurationManager().getConfiguration().getContainer().getInstance(ActionProxyFactory.class);
+        }
+        portletNamespace = cfg.getInitParameter("portletNamespace");
+        if (LOG.isDebugEnabled()) LOG.debug("PortletNamespace: " + portletNamespace);
+        parseModeConfig(actionMap, cfg, PortletMode.VIEW, "viewNamespace",
+                "defaultViewAction");
+        parseModeConfig(actionMap, cfg, PortletMode.EDIT, "editNamespace",
+                "defaultEditAction");
+        parseModeConfig(actionMap, cfg, PortletMode.HELP, "helpNamespace",
+                "defaultHelpAction");
+        parseModeConfig(actionMap, cfg, new PortletMode("config"), "configNamespace",
+                "defaultConfigAction");
+        parseModeConfig(actionMap, cfg, new PortletMode("about"), "aboutNamespace",
+                "defaultAboutAction");
+        parseModeConfig(actionMap, cfg, new PortletMode("print"), "printNamespace",
+                "defaultPrintAction");
+        parseModeConfig(actionMap, cfg, new PortletMode("preview"), "previewNamespace",
+                "defaultPreviewAction");
+        parseModeConfig(actionMap, cfg, new PortletMode("edit_defaults"),
+                "editDefaultsNamespace", "defaultEditDefaultsAction");
+        if (StringUtils.isEmpty(portletNamespace)) {
+            portletNamespace = "";
+        }
+        LocalizedTextUtil
+                .addDefaultResourceBundle("org/apache/struts2/struts-messages");
+
+        container = dispatcherUtils.getContainer();
+        //check for configuration reloading
+        if ("true".equalsIgnoreCase(container.getInstance(String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD))) {
+            FileManager.setReloadingConfigs(true);
+        }
+        
+        actionMapper = container.getInstance(ActionMapper.class);
+    }
+
+    /**
+     * Parse the mode to namespace mappings configured in portlet.xml
+     * @param actionMap The map with mode <-> default action mapping.
+     * @param portletConfig The PortletConfig.
+     * @param portletMode The PortletMode.
+     * @param nameSpaceParam Name of the init parameter where the namespace for the mode
+     * is configured.
+     * @param defaultActionParam Name of the init parameter where the default action to
+     * execute for the mode is configured.
+     */
+    void parseModeConfig(Map<PortletMode, ActionMapping> actionMap, PortletConfig portletConfig,
+            PortletMode portletMode, String nameSpaceParam,
+            String defaultActionParam) {
+        String namespace = portletConfig.getInitParameter(nameSpaceParam);
+        if (StringUtils.isEmpty(namespace)) {
+            namespace = "";
+        }
+        modeMap.put(portletMode, namespace);
+        String defaultAction = portletConfig
+                .getInitParameter(defaultActionParam);
+        String method = null;
+        if (StringUtils.isEmpty(defaultAction)) {
+            defaultAction = DEFAULT_ACTION_NAME;
+        }
+        if(defaultAction.indexOf('!') >= 0) {
+        	method = defaultAction.substring(defaultAction.indexOf('!') + 1);
+        	defaultAction = defaultAction.substring(0, defaultAction.indexOf('!'));
+        }
+        StringBuffer fullPath = new StringBuffer();
+        if (StringUtils.isNotEmpty(portletNamespace)) {
+            fullPath.append(portletNamespace);
+        }
+        if (StringUtils.isNotEmpty(namespace)) {
+            fullPath.append(namespace).append("/");
+        } else {
+            fullPath.append("/");
+        }
+        fullPath.append(defaultAction);
+        ActionMapping mapping = new ActionMapping();
+        mapping.setName(getActionName(fullPath.toString()));
+        mapping.setNamespace(getNamespace(fullPath.toString()));
+        if(method != null) {
+        	mapping.setMethod(method);
+        }
+        actionMap.put(portletMode, mapping);
+    }
+
+    /**
+     * Service an action from the <tt>event</tt> phase.
+     *
+     * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
+     *      javax.portlet.ActionResponse)
+     */
+    public void processAction(ActionRequest request, ActionResponse response)
+            throws PortletException, IOException {
+        if (LOG.isDebugEnabled()) LOG.debug("Entering processAction");
+        resetActionContext();
+        try {
+            serviceAction(request, response, getRequestMap(request), getParameterMap(request),
+                    getSessionMap(request), getApplicationMap(),
+                    portletNamespace, EVENT_PHASE);
+            if (LOG.isDebugEnabled()) LOG.debug("Leaving processAction");
+        } finally {
+            ActionContext.setContext(null);
+        }
+    }
+
+    /**
+     * Service an action from the <tt>render</tt> phase.
+     *
+     * @see javax.portlet.Portlet#render(javax.portlet.RenderRequest,
+     *      javax.portlet.RenderResponse)
+     */
+    public void render(RenderRequest request, RenderResponse response)
+            throws PortletException, IOException {
+
+        if (LOG.isDebugEnabled()) LOG.debug("Entering render");
+        resetActionContext();
+        response.setTitle(getTitle(request));
+        if(!request.getWindowState().equals(WindowState.MINIMIZED)) {
+        try {
+            // Check to see if an event set the render to be included directly
+            serviceAction(request, response, getRequestMap(request), getParameterMap(request),
+                    getSessionMap(request), getApplicationMap(),
+                    portletNamespace, RENDER_PHASE);
+            if (LOG.isDebugEnabled()) LOG.debug("Leaving render");
+        } finally {
+            resetActionContext();
+        }
+        }
+    }
+
+    /**
+     *  Reset the action context.
+     */
+    private void resetActionContext() {
+        ActionContext.setContext(null);
+    }
+    
+    /**
+     * Merges all application and portlet attributes into a single
+     * <tt>HashMap</tt> to represent the entire <tt>Action</tt> context.
+     *
+     * @param requestMap a Map of all request attributes.
+     * @param parameterMap a Map of all request parameters.
+     * @param sessionMap a Map of all session attributes.
+     * @param applicationMap a Map of all servlet context attributes.
+     * @param request the PortletRequest object.
+     * @param response the PortletResponse object.
+     * @param portletConfig the PortletConfig object.
+     * @param phase The portlet phase (render or action, see
+     *        {@link PortletActionConstants})
+     * @return a HashMap representing the <tt>Action</tt> context.
+     */
+    public HashMap<String, Object> createContextMap(Map<String, Object> requestMap, Map<String, String[]> parameterMap,
+            Map<String, Object> sessionMap, Map<String, Object> applicationMap, PortletRequest request,
+            PortletResponse response, HttpServletRequest servletRequest, HttpServletResponse servletResponse, ServletContext servletContext, PortletConfig portletConfig, Integer phase) throws IOException {
+
+        // TODO Must put http request/response objects into map for use with
+    	container.inject(servletRequest);
+    	
+        // ServletActionContext
+        HashMap<String, Object> extraContext = new HashMap<String, Object>();
+        // The dummy servlet objects. Eases reuse of existing interceptors that uses the servlet objects.
+        extraContext.put(StrutsStatics.HTTP_REQUEST, servletRequest);
+        extraContext.put(StrutsStatics.HTTP_RESPONSE, servletResponse);
+        extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
+        // End dummy servlet objects
+        extraContext.put(ActionContext.PARAMETERS, parameterMap);
+        extraContext.put(ActionContext.SESSION, sessionMap);
+        extraContext.put(ActionContext.APPLICATION, applicationMap);
+
+        String defaultLocale = dispatcherUtils.getContainer().getInstance(String.class, StrutsConstants.STRUTS_LOCALE);
+        Locale locale = null;
+        if (defaultLocale != null) {
+            locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
+        } else {
+            locale = request.getLocale();
+        }
+        extraContext.put(ActionContext.LOCALE, locale);
+
+        extraContext.put(StrutsStatics.STRUTS_PORTLET_CONTEXT, getPortletContext());
+        extraContext.put(REQUEST, request);
+        extraContext.put(RESPONSE, response);
+        extraContext.put(PORTLET_CONFIG, portletConfig);
+        extraContext.put(PORTLET_NAMESPACE, portletNamespace);
+        extraContext.put(DEFAULT_ACTION_FOR_MODE, actionMap.get(request.getPortletMode()));
+        // helpers to get access to request/session/application scope
+        extraContext.put("request", requestMap);
+        extraContext.put("session", sessionMap);
+        extraContext.put("application", applicationMap);
+        extraContext.put("parameters", parameterMap);
+        extraContext.put(MODE_NAMESPACE_MAP, modeMap);
+
+        extraContext.put(PHASE, phase);
+
+        AttributeMap attrMap = new AttributeMap(extraContext);
+        extraContext.put("attr", attrMap);
+
+        return extraContext;
+    }
+
+    /**
+     * Loads the action and executes it. This method first creates the action
+     * context from the given parameters then loads an <tt>ActionProxy</tt>
+     * from the given action name and namespace. After that, the action is
+     * executed and output channels throught the response object.
+     *
+     * @param request the HttpServletRequest object.
+     * @param response the HttpServletResponse object.
+     * @param requestMap a Map of request attributes.
+     * @param parameterMap a Map of request parameters.
+     * @param sessionMap a Map of all session attributes.
+     * @param applicationMap a Map of all application attributes.
+     * @param portletNamespace the namespace or context of the action.
+     * @param phase The portlet phase (render or action, see
+     *        {@link PortletActionConstants})
+     */
+    public void serviceAction(PortletRequest request, PortletResponse response, Map<String, Object> requestMap, Map<String, String[]> parameterMap,
+            Map<String, Object> sessionMap, Map<String, Object> applicationMap, String portletNamespace,
+            Integer phase) throws PortletException {
+        if (LOG.isDebugEnabled()) LOG.debug("serviceAction");
+        Dispatcher.setInstance(dispatcherUtils);
+        String actionName = null;
+        String namespace = null;
+        try {
+            ServletContext servletContext = new PortletServletContext(getPortletContext());
+            HttpServletRequest servletRequest = new PortletServletRequest(request, getPortletContext());
+            HttpServletResponse servletResponse = new PortletServletResponse(response);
+            if(EVENT_PHASE.equals(phase)) {
+            	servletRequest = dispatcherUtils.wrapRequest(servletRequest, servletContext);
+        		if(servletRequest instanceof MultiPartRequestWrapper) {
+        			// Multipart request. Request parameters are encoded in the multipart data,
+        			// so we need to manually add them to the parameter map.
+        			parameterMap.putAll(servletRequest.getParameterMap());
+        		}
+        	}
+            container.inject(servletRequest);
+            ActionMapping mapping = getActionMapping(request, servletRequest);
+            actionName = mapping.getName();
+            namespace = mapping.getNamespace();
+            HashMap<String, Object> extraContext = createContextMap(requestMap, parameterMap,
+                    sessionMap, applicationMap, request, response, servletRequest, servletResponse,
+                    servletContext, getPortletConfig(), phase);
+            extraContext.put(PortletActionConstants.ACTION_MAPPING, mapping);
+            if (LOG.isDebugEnabled()) {
+        	LOG.debug("Creating action proxy for name = " + actionName + ", namespace = " + namespace);
+            }
+            ActionProxy proxy = factory.createActionProxy(namespace,
+                    actionName, mapping.getMethod(), extraContext);
+            request.setAttribute("struts.valueStack", proxy.getInvocation()
+                    .getStack());
+            proxy.execute();
+        } catch (ConfigurationException e) {
+            LOG.error("Could not find action", e);
+            throw new PortletException("Could not find action " + actionName, e);
+        } catch (Exception e) {
+            LOG.error("Could not execute action", e);
+            throw new PortletException("Error executing action " + actionName,
+                    e);
+        } finally {
+            Dispatcher.setInstance(null);
+        }
+    }
+
+	/**
+     * Returns a Map of all application attributes. Copies all attributes from
+     * the {@link PortletActionContext}into an {@link ApplicationMap}.
+     *
+     * @return a Map of all application attributes.
+     */
+    protected Map getApplicationMap() {
+        return new PortletApplicationMap(getPortletContext());
+    }
+
+    /**
+     * Gets the namespace of the action from the request. The namespace is the
+     * same as the portlet mode. E.g, view mode is mapped to namespace
+     * <code>view</code>, and edit mode is mapped to the namespace
+     * <code>edit</code>
+     *
+     * @param request the PortletRequest object.
+     * @return the namespace of the action.
+     */
+    protected ActionMapping getActionMapping(final PortletRequest portletRequest, final HttpServletRequest servletRequest) {
+        ActionMapping mapping = null;
+        String actionPath = null;
+        if (resetAction(portletRequest)) {
+            mapping = (ActionMapping) actionMap.get(portletRequest.getPortletMode());
+        } else {
+            actionPath = servletRequest.getParameter(ACTION_PARAM);
+            if (StringUtils.isEmpty(actionPath)) {
+                mapping = (ActionMapping) actionMap.get(portletRequest
+                        .getPortletMode());
+            } else {
+                
+                // Use the usual action mapper, but it is expecting an action extension
+                // on the uri, so we add the default one, which should be ok as the
+                // portlet is a portlet first, a servlet second
+                mapping = actionMapper.getMapping(servletRequest, dispatcherUtils.getConfigurationManager());
+            }
+        }
+        
+        if (mapping == null) {
+            throw new StrutsException("Unable to locate action mapping for request, probably due to " +
+                    "an invalid action path: "+actionPath);
+        }
+        return mapping;
+    }
+
+    /**
+     * Get the namespace part of the action path.
+     * @param actionPath Full path to action
+     * @return The namespace part.
+     */
+    String getNamespace(String actionPath) {
+        int idx = actionPath.lastIndexOf('/');
+        String namespace = "";
+        if (idx >= 0) {
+            namespace = actionPath.substring(0, idx);
+        }
+        return namespace;
+    }
+
+    /**
+     * Get the action name part of the action path.
+     * @param actionPath Full path to action
+     * @return The action name.
+     */
+    String getActionName(String actionPath) {
+        int idx = actionPath.lastIndexOf('/');
+        String action = actionPath;
+        if (idx >= 0) {
+            action = actionPath.substring(idx + 1);
+        }
+        return action;
+    }
+
+    /**
+     * Returns a Map of all request parameters. This implementation just calls
+     * {@link PortletRequest#getParameterMap()}.
+     *
+     * @param request the PortletRequest object.
+     * @return a Map of all request parameters.
+     * @throws IOException if an exception occurs while retrieving the parameter
+     *         map.
+     */
+    protected Map<String, String[]> getParameterMap(PortletRequest request) throws IOException {
+        return new HashMap<String, String[]>(request.getParameterMap());
+    }
+
+    /**
+     * Returns a Map of all request attributes. The default implementation is to
+     * wrap the request in a {@link RequestMap}. Override this method to
+     * customize how request attributes are mapped.
+     *
+     * @param request the PortletRequest object.
+     * @return a Map of all request attributes.
+     */
+    protected Map getRequestMap(PortletRequest request) {
+        return new PortletRequestMap(request);
+    }
+
+    /**
+     * Returns a Map of all session attributes. The default implementation is to
+     * wrap the reqeust in a {@link SessionMap}. Override this method to
+     * customize how session attributes are mapped.
+     *
+     * @param request the PortletRequest object.
+     * @return a Map of all session attributes.
+     */
+    protected Map getSessionMap(PortletRequest request) {
+        return new PortletSessionMap(request);
+    }
+
+    /**
+     * Convenience method to ease testing.
+     * @param factory
+     */
+    protected void setActionProxyFactory(ActionProxyFactory factory) {
+        this.factory = factory;
+    }
+
+    /**
+     * Check to see if the action parameter is valid for the current portlet mode. If the portlet
+     * mode has been changed with the portal widgets, the action name is invalid, since the
+     * action name belongs to the previous executing portlet mode. If this method evaluates to
+     * <code>true</code> the <code>default&lt;Mode&gt;Action</code> is used instead.
+     * @param request The portlet request.
+     * @return <code>true</code> if the action should be reset.
+     */
+    private boolean resetAction(PortletRequest request) {
+        boolean reset = false;
+        Map paramMap = request.getParameterMap();
+        String[] modeParam = (String[]) paramMap.get(MODE_PARAM);
+        if (modeParam != null && modeParam.length == 1) {
+            String originatingMode = modeParam[0];
+            String currentMode = request.getPortletMode().toString();
+            if (!currentMode.equals(originatingMode)) {
+                reset = true;
+            }
+        }
+        if(reset) {
+        	request.setAttribute(ACTION_RESET, Boolean.TRUE);
+        }
+        else {
+        	request.setAttribute(ACTION_RESET, Boolean.FALSE);
+        }
+        return reset;
+    }
+
+    public void destroy() {
+        if (dispatcherUtils == null) {
+            if (LOG.isWarnEnabled()) {
+        	LOG.warn("something is seriously wrong, DispatcherUtil is not initialized (null) ");
+            }
+        } else {
+            dispatcherUtils.cleanup();
+        }
+    }
+
+    /**
+     * @param actionMapper the actionMapper to set
+     */
+    public void setActionMapper(ActionMapper actionMapper) {
+        this.actionMapper = actionMapper;
+    }
+    
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptor.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptor.java
new file mode 100644
index 0000000..b9075c0
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptor.java
@@ -0,0 +1,92 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.interceptor.PrincipalAware;
+import org.apache.struts2.portlet.PortletActionConstants;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+public class PortletAwareInterceptor extends AbstractInterceptor implements PortletActionConstants, StrutsStatics {
+
+	private static final long serialVersionUID = 2476509721059587700L;
+	
+	private static final Logger LOG = LoggerFactory.getLogger(PortletAwareInterceptor.class);
+
+	/**
+     * Sets action properties based on the interfaces an action implements. Things like application properties,
+     * parameters, session attributes, etc are set based on the implementing interface.
+     *
+     * @param invocation an encapsulation of the action execution state.
+     * @throws Exception if an error occurs when setting action properties.
+     */
+    public String intercept(ActionInvocation invocation) throws Exception {
+        final Object action = invocation.getAction();
+        final ActionContext context = invocation.getInvocationContext();
+
+        if (action instanceof PortletRequestAware) {
+            PortletRequest request = (PortletRequest) context.get(REQUEST);
+            ((PortletRequestAware) action).setPortletRequest(request);
+        }
+
+        if (action instanceof PortletResponseAware) {
+            PortletResponse response = (PortletResponse) context.get(RESPONSE);
+            ((PortletResponseAware) action).setPortletResponse(response);
+        }
+        if (action instanceof PrincipalAware) {
+            PortletRequest request = (PortletRequest) context.get(REQUEST);
+            ((PrincipalAware) action).setPrincipalProxy(new PortletPrincipalProxy(request));
+        }
+        if (action instanceof PortletContextAware) {
+            PortletContext portletContext = (PortletContext) context.get(STRUTS_PORTLET_CONTEXT);
+            ((PortletContextAware) action).setPortletContext(portletContext);
+        }
+        if (action instanceof PortletConfigAware) {
+        	PortletConfig portletConfig = (PortletConfig)context.get(PORTLET_CONFIG);
+        	((PortletConfigAware) action).setPortletConfig(portletConfig);
+        }
+        if (action instanceof PortletPreferencesAware) {
+        	PortletRequest request = (PortletRequest) context.get(REQUEST);
+            
+            // Check if running in a servlet environment
+            if (request == null) {
+                if (LOG.isWarnEnabled()) {
+                    LOG.warn("This portlet preferences implementation should only be used during development");
+                }
+                ((PortletPreferencesAware)action).setPortletPreferences(new ServletPortletPreferences(ActionContext.getContext().getSession()));
+            } else {
+            	((PortletPreferencesAware)action).setPortletPreferences(request.getPreferences());
+            }
+        }
+        return invocation.invoke();
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletConfigAware.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletConfigAware.java
new file mode 100644
index 0000000..432de25
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletConfigAware.java
@@ -0,0 +1,34 @@
+/*

+ * $Id: $

+ *

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

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

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

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

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

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

+ *

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

+ *

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

+ * software distributed under the License is distributed on an

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

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

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.struts2.portlet.interceptor;

+

+import javax.portlet.PortletConfig;

+

+

+/**

+ * Actions that wants a reference to the PortletConfig object can

+ * implement this interface.

+ *

+ */

+public interface PortletConfigAware {

+	

+	void setPortletConfig(PortletConfig portletConfig);

+}

diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletContextAware.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletContextAware.java
new file mode 100644
index 0000000..59b613e
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletContextAware.java
@@ -0,0 +1,30 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import javax.portlet.PortletContext;
+
+public interface PortletContextAware {
+
+	void setPortletContext(PortletContext portletContext);
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPreferencesAware.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPreferencesAware.java
new file mode 100644
index 0000000..f692b9c
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPreferencesAware.java
@@ -0,0 +1,40 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import javax.portlet.PortletPreferences;
+
+
+/**
+ * All Actions that want to have access to the portlet preferences should
+ * implement this interface.  If running in a servlet environment, an
+ * appropriate testing implementation will be provided.
+ */
+public interface PortletPreferencesAware {
+
+    /**
+     * Sets the HTTP request object in implementing classes.
+     *
+     * @param request the HTTP request.
+     */
+    public void setPortletPreferences(PortletPreferences prefs);
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPrincipalProxy.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPrincipalProxy.java
new file mode 100644
index 0000000..e1cd082
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletPrincipalProxy.java
@@ -0,0 +1,94 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import org.apache.struts2.interceptor.PrincipalProxy;
+
+import javax.portlet.PortletRequest;
+import javax.servlet.http.HttpServletRequest;
+import java.security.Principal;
+
+/**
+ * PrincipalProxy implementation for using PortletRequest Principal related methods.
+ */
+public class PortletPrincipalProxy implements PrincipalProxy {
+
+    private PortletRequest request;
+
+    /**
+     * Constructs a proxy
+     *
+     * @param request The underlying request
+     */
+    public PortletPrincipalProxy(PortletRequest request) {
+        this.request = request;
+    }
+
+    /**
+     * True if the user is in the given role
+     *
+     * @param role The role
+     * @return True if the user is in that role
+     */
+    public boolean isUserInRole(String role) {
+        return request.isUserInRole(role);
+    }
+
+    /**
+     * Gets the user principal
+     *
+     * @return The principal
+     */
+    public Principal getUserPrincipal() {
+        return request.getUserPrincipal();
+    }
+
+    /**
+     * Gets the user id
+     *
+     * @return The user id
+     */
+    public String getRemoteUser() {
+        return request.getRemoteUser();
+    }
+
+    /**
+     * Is the request using https?
+     *
+     * @return True if using https
+     */
+    public boolean isRequestSecure() {
+        return request.isSecure();
+    }
+
+    /**
+     * Gets the request.
+     *
+     * @return The request
+     * @throws UnsupportedOperationException not supported in this implementation.
+     * @deprecated To obtain the HttpServletRequest in your action, use
+     *             {@link org.apache.struts2.servlet.ServletRequestAware}, since this method will be dropped in future.
+     */
+    public HttpServletRequest getRequest() {
+        throw new UnsupportedOperationException("Usage of getRequest() method is deprecadet and not supported for this implementation");
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletRequestAware.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletRequestAware.java
new file mode 100644
index 0000000..5b3f9b4
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletRequestAware.java
@@ -0,0 +1,30 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import javax.portlet.PortletRequest;
+
+public interface PortletRequestAware {
+
+	void setPortletRequest(PortletRequest request);
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletResponseAware.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletResponseAware.java
new file mode 100644
index 0000000..75a12e8
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletResponseAware.java
@@ -0,0 +1,30 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import javax.portlet.PortletResponse;
+
+public interface PortletResponseAware {
+
+	void setPortletResponse(PortletResponse response);
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptor.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptor.java
new file mode 100644
index 0000000..c6fe6c7
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptor.java
@@ -0,0 +1,95 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.lang.StringUtils;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.dispatcher.DirectRenderFromEventAction;
+
+import javax.portlet.ActionResponse;
+import javax.portlet.RenderRequest;
+import java.util.Map;
+
+public class PortletStateInterceptor extends AbstractInterceptor implements PortletActionConstants {
+
+	private final static Logger LOG = LoggerFactory.getLogger(PortletStateInterceptor.class);
+
+	private static final long serialVersionUID = 6138452063353911784L;
+
+	@Override
+	public String intercept(ActionInvocation invocation) throws Exception {
+		Integer phase = (Integer) invocation.getInvocationContext().get(PHASE);
+		if (RENDER_PHASE.equals(phase)) {
+			restoreStack(invocation);
+			return invocation.invoke();
+		} else if (EVENT_PHASE.equals(phase)) {
+			try {
+				return invocation.invoke();
+			} finally {
+				saveStack(invocation);
+			}
+		} else {
+			return invocation.invoke();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private void saveStack(ActionInvocation invocation) {
+		Map session = invocation.getInvocationContext().getSession();
+		session.put(STACK_FROM_EVENT_PHASE, invocation.getStack());
+		ActionResponse actionResponse = (ActionResponse) invocation.getInvocationContext().get(RESPONSE);
+		actionResponse.setRenderParameter(EVENT_ACTION, "true");
+	}
+
+	@SuppressWarnings("unchecked")
+	private void restoreStack(ActionInvocation invocation) {
+		RenderRequest request = (RenderRequest) invocation.getInvocationContext().get(REQUEST);
+		if (StringUtils.isNotEmpty(request.getParameter(EVENT_ACTION))) {
+			if(!isProperPrg(invocation)) {
+				if (LOG.isDebugEnabled()) LOG.debug("Restoring value stack from event phase");
+				ValueStack oldStack = (ValueStack) invocation.getInvocationContext().getSession().get(
+				STACK_FROM_EVENT_PHASE);
+				if (oldStack != null) {
+					CompoundRoot oldRoot = oldStack.getRoot();
+					ValueStack currentStack = invocation.getStack();
+					CompoundRoot root = currentStack.getRoot();
+					root.addAll(0, oldRoot);
+					if (LOG.isDebugEnabled()) LOG.debug("Restored stack");
+				}
+			}
+			else {
+				if (LOG.isDebugEnabled()) LOG.debug("Won't restore stack from event phase since it's a proper PRG request");
+			}
+		}
+	}
+
+	private boolean isProperPrg(ActionInvocation invocation) {
+		return !(invocation.getAction() instanceof DirectRenderFromEventAction);
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/ServletPortletPreferences.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/ServletPortletPreferences.java
new file mode 100644
index 0000000..9a914b3
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/interceptor/ServletPortletPreferences.java
@@ -0,0 +1,96 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.portlet.PortletPreferences;
+import javax.portlet.ReadOnlyException;
+import javax.portlet.ValidatorException;
+
+/**
+ * Simple portlet preferences implementation that uses a map in the Session
+ * as storage.
+ */
+public class ServletPortletPreferences implements PortletPreferences {
+
+    private Map session;
+    private String PREFERENCES_KEY = "_portlet-preferences";
+    
+    public ServletPortletPreferences(Map session) {
+        this.session = session;
+    }
+    
+    public Map getMap() {
+        Map map = (Map) session.get(PREFERENCES_KEY);
+        if (map == null) {
+            map = new HashMap();
+            session.put(PREFERENCES_KEY, map);
+        }
+        return map;
+    }
+
+    public Enumeration getNames() {
+        return new Vector(getMap().keySet()).elements();
+    }
+
+    public String getValue(String key, String def) {
+        String val = (String) getMap().get(key);
+        if (val == null) {
+            val = def;
+        }
+        return val;
+    }
+
+    public String[] getValues(String key, String[] def) {
+        String[] val = (String[]) getMap().get(key);
+        if (val == null) {
+            val = def;
+        }
+        return val;
+    }
+
+    public boolean isReadOnly(String arg0) {
+        return false;
+    }
+
+    public void reset(String arg0) throws ReadOnlyException {
+        session.put(PREFERENCES_KEY, new HashMap());
+    }
+
+    public void setValue(String key, String value) throws ReadOnlyException {
+        getMap().put(key, value);
+    }
+
+    public void setValues(String key, String[] value) throws ReadOnlyException {
+        getMap().put(key, value);
+    }
+
+    public void store() throws IOException, ValidatorException {
+        
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletActionRedirectResult.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletActionRedirectResult.java
new file mode 100644
index 0000000..d794a1f
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletActionRedirectResult.java
@@ -0,0 +1,269 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.result;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.portlet.PortletMode;
+
+import org.apache.struts2.dispatcher.ServletActionRedirectResult;
+import org.apache.struts2.dispatcher.mapper.ActionMapper;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.views.util.UrlHelper;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
+import com.opensymphony.xwork2.util.reflection.ReflectionException;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Inject;
+
+/**
+ *
+ * Portlet modification of the {@link ServletActionRedirectResult}.
+ *
+ * <!-- START SNIPPET: description -->
+ *
+ * This result uses the {@link ActionMapper} provided by the
+ * {@link ActionMapperFactory} to instruct the render phase to invoke the
+ * specified action and (optional) namespace. This is better than the
+ * {@link PortletResult} because it does not require you to encode the URL
+ * patterns processed by the {@link ActionMapper} in to your struts.xml
+ * configuration files. This means you can change your URL patterns at any point
+ * and your application will still work. It is strongly recommended that if you
+ * are redirecting to another action, you use this result rather than the
+ * standard redirect result.
+ *
+ * See examples below for an example of how request parameters could be passed
+ * in.
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <b>This result type takes the following parameters:</b>
+ *
+ * <!-- START SNIPPET: params -->
+ *
+ * <ul>
+ *
+ * <li><b>actionName (default)</b> - the name of the action that will be
+ * redirect to</li>
+ *
+ * <li><b>namespace</b> - used to determine which namespace the action is in
+ * that we're redirecting to . If namespace is null, this defaults to the
+ * current namespace</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: params -->
+ *
+ * <b>Example:</b>
+ *
+ * <pre>
+ * &lt;!-- START SNIPPET: example --&gt;
+ *  &lt;package name=&quot;public&quot; extends=&quot;struts-default&quot;&gt;
+ *      &lt;action name=&quot;login&quot; class=&quot;...&quot;&gt;
+ *          &lt;!-- Redirect to another namespace --&gt;
+ *          &lt;result type=&quot;redirectAction&quot;&gt;
+ *              &lt;param name=&quot;actionName&quot;&gt;dashboard&lt;/param&gt;
+ *              &lt;param name=&quot;namespace&quot;&gt;/secure&lt;/param&gt;
+ *          &lt;/result&gt;
+ *      &lt;/action&gt;
+ *  &lt;/package&gt;
+ *
+ *  &lt;package name=&quot;secure&quot; extends=&quot;struts-default&quot; namespace=&quot;/secure&quot;&gt;
+ *      &lt;-- Redirect to an action in the same namespace --&gt;
+ *      &lt;action name=&quot;dashboard&quot; class=&quot;...&quot;&gt;
+ *          &lt;result&gt;dashboard.jsp&lt;/result&gt;
+ *          &lt;result name=&quot;error&quot; type=&quot;redirectAction&quot;&gt;error&lt;/result&gt;
+ *      &lt;/action&gt;
+ *
+ *      &lt;action name=&quot;error&quot; class=&quot;...&quot;&gt;
+ *          &lt;result&gt;error.jsp&lt;/result&gt;
+ *      &lt;/action&gt;
+ *  &lt;/package&gt;
+ *
+ *  &lt;package name=&quot;passingRequestParameters&quot; extends=&quot;struts-default&quot; namespace=&quot;/passingRequestParameters&quot;&gt;
+ *     &lt;-- Pass parameters (reportType, width and height) --&gt;
+ *     &lt;!--
+ *     The redirectAction url generated will be :
+ *     /genReport/generateReport.action?reportType=pie&amp;width=100&amp;height=100
+ *     --&gt;
+ *     &lt;action name=&quot;gatherReportInfo&quot; class=&quot;...&quot;&gt;
+ *        &lt;result name=&quot;showReportResult&quot; type=&quot;redirectAction&quot;&gt;
+ *           &lt;param name=&quot;actionName&quot;&gt;generateReport&lt;/param&gt;
+ *           &lt;param name=&quot;namespace&quot;&gt;/genReport&lt;/param&gt;
+ *           &lt;param name=&quot;reportType&quot;&gt;pie&lt;/param&gt;
+ *           &lt;param name=&quot;width&quot;&gt;100&lt;/param&gt;
+ *           &lt;param name=&quot;height&quot;&gt;100&lt;/param&gt;
+ *        &lt;/result&gt;
+ *     &lt;/action&gt;
+ *  &lt;/package&gt;
+ *
+ *
+ *  &lt;!-- END SNIPPET: example --&gt;
+ * </pre>
+ *
+ * @see ActionMapper
+ */
+public class PortletActionRedirectResult extends PortletResult implements ReflectionExceptionHandler {
+
+	private static final long serialVersionUID = -7627388936683562557L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortletActionRedirectResult.class);
+
+    /** The default parameter */
+	public static final String DEFAULT_PARAM = "actionName";
+
+	protected String actionName;
+
+	protected String namespace;
+
+	protected String method;
+
+	private Map<String, String> requestParameters = new LinkedHashMap<String, String>();
+
+	private ActionMapper actionMapper;
+
+	public PortletActionRedirectResult() {
+		super();
+	}
+
+	public PortletActionRedirectResult(String actionName) {
+		this(null, actionName, null);
+	}
+
+	public PortletActionRedirectResult(String actionName, String method) {
+		this(null, actionName, method);
+	}
+
+	public PortletActionRedirectResult(String namespace, String actionName, String method) {
+		super(null);
+		this.namespace = namespace;
+		this.actionName = actionName;
+		this.method = method;
+	}
+
+	protected List<String> prohibitedResultParam = Arrays.asList(new String[] { DEFAULT_PARAM, "namespace", "method",
+			"encode", "parse", "location", "prependServletContext" });
+
+	@Inject
+	public void setActionMapper(ActionMapper actionMapper) {
+		this.actionMapper = actionMapper;
+	}
+
+	/**
+	 * @see com.opensymphony.xwork2.Result#execute(com.opensymphony.xwork2.ActionInvocation)
+	 */
+	public void execute(ActionInvocation invocation) throws Exception {
+		actionName = conditionalParse(actionName, invocation);
+		if (portletMode != null) {
+			Map<PortletMode, String> namespaceMap = (Map<PortletMode, String>) invocation.getInvocationContext().get(
+					PortletActionConstants.MODE_NAMESPACE_MAP);
+			namespace = namespaceMap.get(portletMode);
+		}
+		if (namespace == null) {
+			namespace = invocation.getProxy().getNamespace();
+		} else {
+			namespace = conditionalParse(namespace, invocation);
+		}
+		if (method == null) {
+			method = "";
+		} else {
+			method = conditionalParse(method, invocation);
+		}
+
+		String resultCode = invocation.getResultCode();
+		if (resultCode != null) {
+			ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(resultCode);
+			Map resultConfigParams = resultConfig.getParams();
+			for (Iterator i = resultConfigParams.entrySet().iterator(); i.hasNext();) {
+				Map.Entry e = (Map.Entry) i.next();
+				if (!prohibitedResultParam.contains(e.getKey())) {
+					requestParameters.put(e.getKey().toString(), e.getValue() == null ? "" : conditionalParse(e
+							.getValue().toString(), invocation));
+				}
+			}
+		}
+
+		StringBuilder tmpLocation = new StringBuilder(actionMapper.getUriFromActionMapping(new ActionMapping(actionName,
+                namespace, method, null)));
+		UrlHelper.buildParametersString(requestParameters, tmpLocation, "&");
+
+		setLocation(tmpLocation.toString());
+
+		super.execute(invocation);
+	}
+
+	/**
+	 * Sets the action name
+	 *
+	 * @param actionName
+	 *            The name
+	 */
+	public void setActionName(String actionName) {
+		this.actionName = actionName;
+	}
+
+	/**
+	 * Sets the namespace
+	 *
+	 * @param namespace
+	 *            The namespace
+	 */
+	public void setNamespace(String namespace) {
+		this.namespace = namespace;
+	}
+
+	/**
+	 * Sets the method
+	 *
+	 * @param method
+	 *            The method
+	 */
+	public void setMethod(String method) {
+		this.method = method;
+	}
+
+	/**
+	 * Adds a request parameter to be added to the redirect url
+	 *
+	 * @param key
+	 *            The parameter name
+	 * @param value
+	 *            The parameter value
+	 */
+	public PortletActionRedirectResult addParameter(String key, Object value) {
+		requestParameters.put(key, String.valueOf(value));
+		return this;
+	}
+
+    public void handle(ReflectionException ex) {
+        // Only log as debug as they are probably parameters to be appended to the url
+        if (LOG.isDebugEnabled()) LOG.debug(ex.getMessage(), ex);
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletResult.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletResult.java
new file mode 100644
index 0000000..7e949ac
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletResult.java
@@ -0,0 +1,243 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.result;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.lang.StringUtils;
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.dispatcher.StrutsResultSupport;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.context.PortletActionContext;
+
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletException;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * Result type that includes a JSP to render.
+ * 
+ */
+public class PortletResult extends StrutsResultSupport implements PortletActionConstants {
+
+	private static final long serialVersionUID = 434251393926178567L;
+
+	private boolean useDispatcherServlet;
+	
+	private String dispatcherServletName = DEFAULT_DISPATCHER_SERVLET_NAME;
+
+	/**
+	 * Logger instance.
+	 */
+	private static final Logger LOG = LoggerFactory.getLogger(PortletResult.class);
+
+	private String contentType = "text/html";
+
+	private String title;
+	
+	protected PortletMode portletMode;
+
+	public PortletResult() {
+		super();
+	}
+
+	public PortletResult(String location) {
+		super(location);
+	}
+
+	/**
+	 * Execute the result. Obtains the
+	 * {@link javax.portlet.PortletRequestDispatcher}from the
+	 * {@link PortletActionContext}and includes the JSP.
+	 * 
+	 * @see com.opensymphony.xwork2.Result#execute(com.opensymphony.xwork2.ActionInvocation)
+	 */
+	public void doExecute(String finalLocation, ActionInvocation actionInvocation) throws Exception {
+
+		if (PortletActionContext.isRender()) {
+			executeRenderResult(finalLocation);
+		} else if (PortletActionContext.isEvent()) {
+			executeActionResult(finalLocation, actionInvocation);
+		} else {
+			executeRegularServletResult(finalLocation, actionInvocation);
+		}
+	}
+
+	/**
+	 * Executes the regular servlet result.
+	 * 
+	 * @param finalLocation
+	 * @param actionInvocation
+	 */
+	private void executeRegularServletResult(String finalLocation, ActionInvocation actionInvocation)
+			throws ServletException, IOException {
+		ServletContext ctx = ServletActionContext.getServletContext();
+		HttpServletRequest req = ServletActionContext.getRequest();
+		HttpServletResponse res = ServletActionContext.getResponse();
+		try {
+			ctx.getRequestDispatcher(finalLocation).include(req, res);
+		} catch (ServletException e) {
+			LOG.error("ServletException including " + finalLocation, e);
+			throw e;
+		} catch (IOException e) {
+			LOG.error("IOException while including result '" + finalLocation + "'", e);
+			throw e;
+		}
+	}
+
+	/**
+	 * Executes the action result.
+	 * 
+	 * @param finalLocation
+	 * @param invocation
+	 */
+	protected void executeActionResult(String finalLocation, ActionInvocation invocation) throws Exception {
+		if (LOG.isDebugEnabled()) LOG.debug("Executing result in Event phase");
+		ActionResponse res = PortletActionContext.getActionResponse();
+		Map sessionMap = invocation.getInvocationContext().getSession();
+		if (LOG.isDebugEnabled()) LOG.debug("Setting event render parameter: " + finalLocation);
+		if (finalLocation.indexOf('?') != -1) {
+			convertQueryParamsToRenderParams(res, finalLocation.substring(finalLocation.indexOf('?') + 1));
+			finalLocation = finalLocation.substring(0, finalLocation.indexOf('?'));
+		}
+		if (finalLocation.endsWith(".action")) {
+			// View is rendered with a view action...luckily...
+			finalLocation = finalLocation.substring(0, finalLocation.lastIndexOf("."));
+			res.setRenderParameter(ACTION_PARAM, finalLocation);
+		} else {
+			// View is rendered outside an action...uh oh...
+            String namespace = invocation.getProxy().getNamespace();
+            if ( namespace != null && namespace.length() > 0 && !namespace.endsWith("/")) {
+                namespace += "/";
+                
+            }
+            res.setRenderParameter(ACTION_PARAM, namespace + "renderDirect");
+			sessionMap.put(RENDER_DIRECT_LOCATION, finalLocation);
+		}
+		if(portletMode != null) {
+			res.setPortletMode(portletMode);
+			res.setRenderParameter(PortletActionConstants.MODE_PARAM, portletMode.toString());
+		}
+		else {
+			res.setRenderParameter(PortletActionConstants.MODE_PARAM, PortletActionContext.getRequest().getPortletMode()
+					.toString());
+		}
+	}
+
+	/**
+	 * Converts the query params to render params.
+	 * 
+	 * @param response
+	 * @param queryParams
+	 */
+	protected static void convertQueryParamsToRenderParams(ActionResponse response, String queryParams) {
+		StringTokenizer tok = new StringTokenizer(queryParams, "&");
+		while (tok.hasMoreTokens()) {
+			String token = tok.nextToken();
+			String key = token.substring(0, token.indexOf('='));
+			String value = token.substring(token.indexOf('=') + 1);
+			response.setRenderParameter(key, value);
+		}
+	}
+
+	/**
+	 * Executes the render result.
+	 * 
+	 * @param finalLocation
+	 * @throws PortletException
+	 * @throws IOException
+	 */
+	protected void executeRenderResult(final String finalLocation) throws PortletException, IOException {
+		if (LOG.isDebugEnabled()) LOG.debug("Executing result in Render phase");
+		PortletContext ctx = PortletActionContext.getPortletContext();
+		RenderRequest req = PortletActionContext.getRenderRequest();
+		RenderResponse res = PortletActionContext.getRenderResponse();
+		res.setContentType(contentType);
+		if (StringUtils.isNotEmpty(title)) {
+			res.setTitle(title);
+		}
+		if (LOG.isDebugEnabled()) LOG.debug("Location: " + finalLocation);
+		if (useDispatcherServlet) {
+			req.setAttribute(DISPATCH_TO, finalLocation);
+			PortletRequestDispatcher dispatcher = ctx.getNamedDispatcher(dispatcherServletName);
+			if(dispatcher == null) {
+				throw new PortletException("Could not locate dispatcher servlet \"" + dispatcherServletName + "\". Please configure it in your web.xml file");
+			}
+			dispatcher.include(req, res);
+		} else {
+			PortletRequestDispatcher dispatcher = ctx.getRequestDispatcher(finalLocation);
+			if (dispatcher == null) {
+				throw new PortletException("Could not locate dispatcher for '" + finalLocation + "'");
+			}
+			dispatcher.include(req, res);
+		}
+	}
+
+	/**
+	 * Sets the content type.
+	 * 
+	 * @param contentType
+	 *            The content type to set.
+	 */
+	public void setContentType(String contentType) {
+		this.contentType = contentType;
+	}
+
+	/**
+	 * Sets the title.
+	 * 
+	 * @param title
+	 *            The title to set.
+	 */
+	public void setTitle(String title) {
+		this.title = title;
+	}
+	
+	public void setPortletMode(String portletMode) {
+		if(portletMode != null) {
+			this.portletMode = new PortletMode(portletMode);
+		}
+	}
+
+	@Inject("struts.portlet.useDispatcherServlet") 
+	public void setUseDispatcherServlet(String useDispatcherServlet) {
+		this.useDispatcherServlet = "true".equalsIgnoreCase(useDispatcherServlet);
+	}
+	
+	@Inject("struts.portlet.dispatcherServletName")
+	public void setDispatcherServletName(String dispatcherServletName) {
+		this.dispatcherServletName = dispatcherServletName;
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletVelocityResult.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletVelocityResult.java
new file mode 100644
index 0000000..33defd8
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/result/PortletVelocityResult.java
@@ -0,0 +1,291 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.result;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.StrutsResultSupport;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.context.PortletActionContext;
+import org.apache.struts2.views.JspSupportServlet;
+import org.apache.struts2.views.velocity.VelocityManager;
+import org.apache.velocity.Template;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+
+import javax.portlet.ActionResponse;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.PageContext;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * Using the Servlet container's {@link JspFactory}, this result mocks a JSP
+ * execution environment and then displays a Velocity template that will be
+ * streamed directly to the servlet output.
+ *
+ * <!-- END SNIPPET: description --> <p/><b>This result type takes the
+ * following parameters: </b>
+ *
+ * <!-- START SNIPPET: params -->
+ *
+ * <ul>
+ *
+ * <li><b>location (default) </b>- the location of the template to process.
+ * </li>
+ *
+ * <li><b>parse </b>- true by default. If set to false, the location param
+ * will not be parsed for Ognl expressions.</li>
+ *
+ * </ul>
+ * <p>
+ * This result follows the same rules from {@link StrutsResultSupport}.
+ * </p>
+ *
+ * <!-- END SNIPPET: params -->
+ *
+ * <b>Example: </b>
+ *
+ * <pre>
+ * &lt;!-- START SNIPPET: example --&gt;
+ *  &lt;result name=&quot;success&quot; type=&quot;velocity&quot;&gt;
+ *    &lt;param name=&quot;location&quot;&gt;foo.vm&lt;/param&gt;
+ *  &lt;/result&gt;
+ *  &lt;!-- END SNIPPET: example --&gt;
+ * </pre>
+ *
+ */
+public class PortletVelocityResult extends StrutsResultSupport {
+
+    private static final long serialVersionUID = -8241086555872212274L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortletVelocityResult.class);
+    
+    private String defaultEncoding;
+    private VelocityManager velocityManager;
+    private JspFactory jspFactory = JspFactory.getDefaultFactory();
+
+    public PortletVelocityResult() {
+        super();
+    }
+
+    public PortletVelocityResult(String location) {
+        super(location);
+    }
+    
+    @Inject
+    public void setVelocityManager(VelocityManager mgr) {
+        this.velocityManager = mgr;
+    }
+    
+    @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
+    public void setDefaultEncoding(String encoding) {
+        this.defaultEncoding = encoding;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.struts2.dispatcher.StrutsResultSupport#doExecute(java.lang.String, com.opensymphony.xwork2.ActionInvocation)
+     */
+    public void doExecute(String location, ActionInvocation invocation)
+            throws Exception {
+        if (PortletActionContext.isEvent()) {
+            executeActionResult(location, invocation);
+        } else if (PortletActionContext.isRender()) {
+            executeRenderResult(location, invocation);
+        }
+    }
+
+    /**
+     * Executes the result
+     *
+     * @param location The location string
+     * @param invocation The action invocation
+     */
+    private void executeActionResult(String location,
+            ActionInvocation invocation) {
+        ActionResponse res = PortletActionContext.getActionResponse();
+        // View is rendered outside an action...uh oh...
+        String namespace = invocation.getProxy().getNamespace();
+        if ( namespace != null && namespace.length() > 0 && !namespace.endsWith("/")) {
+            namespace += "/";
+
+        }
+        res.setRenderParameter(PortletActionConstants.ACTION_PARAM, namespace + "freemarkerDirect");
+        res.setRenderParameter("location", location);
+        res.setRenderParameter(PortletActionConstants.MODE_PARAM, PortletActionContext
+                .getRequest().getPortletMode().toString());
+
+    }
+
+    /**
+     * Creates a Velocity context from the action, loads a Velocity template and
+     * executes the template. Output is written to the servlet output stream.
+     *
+     * @param finalLocation the location of the Velocity template
+     * @param invocation an encapsulation of the action execution state.
+     * @throws Exception if an error occurs when creating the Velocity context,
+     *         loading or executing the template or writing output to the
+     *         servlet response stream.
+     */
+    public void executeRenderResult(String finalLocation,
+            ActionInvocation invocation) throws Exception {
+        ValueStack stack = ActionContext.getContext().getValueStack();
+
+        HttpServletRequest request = ServletActionContext.getRequest();
+        HttpServletResponse response = ServletActionContext.getResponse();
+        ServletContext servletContext = ServletActionContext
+                .getServletContext();
+        Servlet servlet = JspSupportServlet.jspSupportServlet;
+
+        velocityManager.init(servletContext);
+
+        boolean usedJspFactory = false;
+        PageContext pageContext = (PageContext) ActionContext.getContext().get(
+                ServletActionContext.PAGE_CONTEXT);
+
+        if (pageContext == null && servlet != null) {
+            pageContext = jspFactory.getPageContext(servlet, request, response,
+                    null, true, 8192, true);
+            ActionContext.getContext().put(ServletActionContext.PAGE_CONTEXT,
+                    pageContext);
+            usedJspFactory = true;
+        }
+
+        try {
+            String encoding = getEncoding(finalLocation);
+            String contentType = getContentType(finalLocation);
+
+            if (encoding != null) {
+                contentType = contentType + ";charset=" + encoding;
+            }
+            response.setContentType(contentType);
+            Template t = getTemplate(stack,
+                    velocityManager.getVelocityEngine(), invocation,
+                    finalLocation, encoding);
+
+            Context context = createContext(velocityManager, stack, request,
+                    response, finalLocation);
+            Writer writer = new OutputStreamWriter(response.getOutputStream(),
+                    encoding);
+
+            t.merge(context, writer);
+
+            // always flush the writer (we used to only flush it if this was a
+            // jspWriter, but someone asked
+            // to do it all the time (WW-829). Since Velocity support is being
+            // deprecated, we'll oblige :)
+            writer.flush();
+        } catch (Exception e) {
+            LOG.error("Unable to render Velocity Template, '" + finalLocation
+                    + "'", e);
+            throw e;
+        } finally {
+            if (usedJspFactory) {
+                jspFactory.releasePageContext(pageContext);
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * Retrieve the content type for this template. <p/>People can override
+     * this method if they want to provide specific content types for specific
+     * templates (eg text/xml).
+     *
+     * @return The content type associated with this template (default
+     *         "text/html")
+     */
+    protected String getContentType(String templateLocation) {
+        return "text/html";
+    }
+
+    /**
+     * Retrieve the encoding for this template. <p/>People can override this
+     * method if they want to provide specific encodings for specific templates.
+     *
+     * @return The encoding associated with this template (defaults to the value
+     *         of 'struts.i18n.encoding' property)
+     */
+    protected String getEncoding(String templateLocation) {
+        String encoding = defaultEncoding;
+        if (encoding == null) {
+            encoding = System.getProperty("file.encoding");
+        }
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+        return encoding;
+    }
+
+    /**
+     * Given a value stack, a Velocity engine, and an action invocation, this
+     * method returns the appropriate Velocity template to render.
+     *
+     * @param stack the value stack to resolve the location again (when parse
+     *        equals true)
+     * @param velocity the velocity engine to process the request against
+     * @param invocation an encapsulation of the action execution state.
+     * @param location the location of the template
+     * @param encoding the charset encoding of the template
+     * @return the template to render
+     * @throws Exception when the requested template could not be found
+     */
+    protected Template getTemplate(ValueStack stack,
+            VelocityEngine velocity, ActionInvocation invocation,
+            String location, String encoding) throws Exception {
+        if (!location.startsWith("/")) {
+            location = invocation.getProxy().getNamespace() + "/" + location;
+        }
+
+        Template template = velocity.getTemplate(location, encoding);
+
+        return template;
+    }
+
+    /**
+     * Creates the VelocityContext that we'll use to render this page.
+     *
+     * @param velocityManager a reference to the velocityManager to use
+     * @param stack the value stack to resolve the location against (when parse
+     *        equals true)
+     * @param location the name of the template that is being used
+     * @return the a minted Velocity context.
+     */
+    protected Context createContext(VelocityManager velocityManager,
+            ValueStack stack, HttpServletRequest request,
+            HttpServletResponse response, String location) {
+        return velocityManager.createContext(stack, request, response);
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletHttpSession.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletHttpSession.java
new file mode 100644
index 0000000..ebc39fa
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletHttpSession.java
@@ -0,0 +1,215 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.portlet.PortletSession;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+/**
+ * Wrapper object exposing a {@link PortletSession} as a {@link HttpSession} instance.
+ * Clients accessing this session object will in fact operate on the
+ * {@link PortletSession} object wrapped by this session object.
+ */
+public class PortletHttpSession implements HttpSession {
+
+	private PortletSession portletSession;
+
+	public PortletHttpSession(PortletSession portletSession) {
+		this.portletSession = portletSession;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getAttribute(java.lang.String)
+	 */
+	public Object getAttribute(String name) {
+		return portletSession.getAttribute(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getAttributeNames()
+	 */
+	public Enumeration getAttributeNames() {
+		return portletSession.getAttributeNames();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getCreationTime()
+	 */
+	public long getCreationTime() {
+		return portletSession.getCreationTime();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getId()
+	 */
+	public String getId() {
+		return portletSession.getId();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getLastAccessedTime()
+	 */
+	public long getLastAccessedTime() {
+		return portletSession.getLastAccessedTime();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getMaxInactiveInterval()
+	 */
+	public int getMaxInactiveInterval() {
+		return portletSession.getMaxInactiveInterval();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getServletContext()
+	 */
+	public ServletContext getServletContext() {
+		return new PortletServletContext(portletSession.getPortletContext());
+	}
+
+	/**
+	 * @see javax.servlet.http.HttpSession#getSessionContext()
+	 * @throws IllegalStateException
+	 *             Not supported in a portlet.
+	 */
+	public HttpSessionContext getSessionContext() {
+		throw new IllegalStateException("Not supported in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getValue(java.lang.String)
+	 */
+	public Object getValue(String name) {
+		return getAttribute(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#getValueNames()
+	 */
+	public String[] getValueNames() {
+		List<String> names = new ArrayList<String>();
+		Enumeration attrNames = getAttributeNames();
+		while (attrNames.hasMoreElements()) {
+			names.add((String) attrNames.nextElement());
+		}
+		return names.toArray(new String[0]);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#invalidate()
+	 */
+	public void invalidate() {
+		portletSession.invalidate();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#isNew()
+	 */
+	public boolean isNew() {
+		return portletSession.isNew();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#putValue(java.lang.String,
+	 *      java.lang.Object)
+	 */
+	public void putValue(String name, Object value) {
+		setAttribute(name, value);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#removeAttribute(java.lang.String)
+	 */
+	public void removeAttribute(String name) {
+		portletSession.removeAttribute(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#removeValue(java.lang.String)
+	 */
+	public void removeValue(String name) {
+		removeAttribute(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#setAttribute(java.lang.String,
+	 *      java.lang.Object)
+	 */
+	public void setAttribute(String name, Object value) {
+		portletSession.setAttribute(name, value);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpSession#setMaxInactiveInterval(int)
+	 */
+	public void setMaxInactiveInterval(int interval) {
+		portletSession.setMaxInactiveInterval(interval);
+	}
+
+	/**
+	 * Get the wrapped portlet session.
+	 * 
+	 * @return The wrapped portlet session.
+	 */
+	public PortletSession getPortletSession() {
+		return portletSession;
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletConfig.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletConfig.java
new file mode 100644
index 0000000..ea09237
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletConfig.java
@@ -0,0 +1,82 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.util.Enumeration;
+
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * Wrapper object exposing a {@link PortletConfig} as a {@link ServletConfig} instance.
+ * Clients accessing this config object will in fact operate on the
+ * {@link PortletConfig} object wrapped by this config object.
+ */
+public class PortletServletConfig implements ServletConfig {
+
+	private PortletConfig portletConfig;
+	
+	public PortletServletConfig(PortletConfig portletConfig) {
+		this.portletConfig = portletConfig;
+	}
+	
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletConfig#getInitParameter(java.lang.String)
+	 */
+	public String getInitParameter(String name) {
+		return portletConfig.getInitParameter(name);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletConfig#getInitParameterNames()
+	 */
+	public Enumeration getInitParameterNames() {
+		return portletConfig.getInitParameterNames();
+	}
+
+	/**
+	 * Get the {@link PortletContext} as a {@link PortletServletContext} instance.
+	 * @see javax.servlet.ServletConfig#getServletContext()
+	 */
+	public ServletContext getServletContext() {
+		return new PortletServletContext(portletConfig.getPortletContext());
+	}
+
+	/**
+	 * Will return the portlet name.
+	 * @see javax.servlet.ServletConfig#getServletName()
+	 */
+	public String getServletName() {
+		return portletConfig.getPortletName();
+	}
+	
+	/**
+	 * Get the wrapped {@link PortletConfig} instance.
+	 * @return The wrapped {@link PortletConfig} instance.
+	 */
+	public PortletConfig getPortletConfig() {
+		return portletConfig;
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletContext.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletContext.java
new file mode 100644
index 0000000..6b86287
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletContext.java
@@ -0,0 +1,236 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Set;
+
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequestDispatcher;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/**
+ * Wrapper object exposing a {@link PortletContext} as a {@link ServletContext} instance.
+ * Clients accessing this context object will in fact operate on the
+ * {@link PortletContext} object wrapped by this context object.
+ */
+public class PortletServletContext implements ServletContext {
+
+	private PortletContext portletContext;
+	
+	public PortletServletContext(PortletContext portletContext) {
+		this.portletContext = portletContext;
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+	 */
+	public Object getAttribute(String name) {
+		return portletContext.getAttribute(name);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getAttributeNames()
+	 */
+	public Enumeration getAttributeNames() {
+		return portletContext.getAttributeNames();
+	}
+
+	/**
+	 * @see javax.servlet.ServletContext#getContext(java.lang.String)
+	 * @throws IllegalStateException Not supported in a portlet.
+	 */
+	public ServletContext getContext(String uripath) {
+		throw new IllegalStateException("Not supported in a portlet");
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+	 */
+	public String getInitParameter(String name) {
+		return portletContext.getInitParameter(name);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getInitParameterNames()
+	 */
+	public Enumeration getInitParameterNames() {
+		return portletContext.getInitParameterNames();
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getMajorVersion()
+	 */
+	public int getMajorVersion() {
+		return portletContext.getMajorVersion();
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+	 */
+	public String getMimeType(String file) {
+		return portletContext.getMimeType(file);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getMinorVersion()
+	 */
+	public int getMinorVersion() {
+		return portletContext.getMinorVersion();
+	}
+
+	/**
+	 * Returns a {@link PortletServletRequestDispatcher} wrapping the {@link PortletRequestDispatcher}
+	 * as a {@link RequestDispatcher} instance.
+	 * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+	 * @return PortletServletRequestDispatcher
+	 */
+	public RequestDispatcher getNamedDispatcher(String name) {
+		return new PortletServletRequestDispatcher(portletContext.getNamedDispatcher(name));
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+	 */
+	public String getRealPath(String path) {
+		return portletContext.getRealPath(path);
+	}
+
+	/**
+	 * Returns a {@link PortletServletRequestDispatcher} wrapping the {@link PortletRequestDispatcher}
+	 * as a {@link RequestDispatcher} instance.
+	 * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+	 * @return PortletServletRequestDispatcher
+	 */
+	public RequestDispatcher getRequestDispatcher(String path) {
+		return new PortletServletRequestDispatcher(portletContext.getRequestDispatcher(path));
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getResource(java.lang.String)
+	 */
+	public URL getResource(String path) throws MalformedURLException {
+		return portletContext.getResource(path);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+	 */
+	public InputStream getResourceAsStream(String path) {
+		return portletContext.getResourceAsStream(path);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+	 */
+	public Set getResourcePaths(String path) {
+		return portletContext.getResourcePaths(path);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getServerInfo()
+	 */
+	public String getServerInfo() {
+		return portletContext.getServerInfo();
+	}
+
+	/**
+	 * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+	 * @throws IllegalStateException Not supported in a portlet.
+	 */
+	public Servlet getServlet(String name) throws ServletException {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#getServletContextName()
+	 */
+	public String getServletContextName() {
+		return portletContext.getPortletContextName();
+	}
+
+	/**
+	 * @see javax.servlet.ServletContext#getServletNames()
+ 	 * @throws IllegalStateException Not supported in a portlet.
+	 */
+	public Enumeration getServletNames() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * @see javax.servlet.ServletContext#getServlets()
+	 * @throws IllegalStateException Not supported in a portlet.
+	 */
+	public Enumeration getServlets() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#log(java.lang.String)
+	 */
+	public void log(String msg) {
+		portletContext.log(msg);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+	 */
+	public void log(Exception exception, String msg) {
+		log(msg, exception);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+	 */
+	public void log(String message, Throwable throwable) {
+		portletContext.log(message, throwable);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+	 */
+	public void removeAttribute(String name) {
+		portletContext.removeAttribute(name);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+	 */
+	public void setAttribute(String name, Object object) {
+		portletContext.setAttribute(name, object);
+	}
+	
+	/**
+	 * Get the wrapped {@link PortletContext} instance.
+	 * @return The wrapped {@link PortletContext} instance.
+	 */
+	public PortletContext getPortletContext() {
+		return portletContext;
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletInputStream.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletInputStream.java
new file mode 100644
index 0000000..a55f966
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletInputStream.java
@@ -0,0 +1,122 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.ServletInputStream;
+
+/**
+ * Wrapper object exposing a {@link InputStream} from a portlet as a {@link ServletInputStream} instance.
+ * Clients accessing this stream object will in fact operate on the
+ * {@link InputStream} object wrapped by this stream object.
+ */
+public class PortletServletInputStream extends ServletInputStream {
+
+	private InputStream portletInputStream;
+	
+	public PortletServletInputStream(InputStream portletInputStream) {
+		this.portletInputStream = portletInputStream;
+	}
+	
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#read()
+	 */
+	@Override
+	public int read() throws IOException {
+		return portletInputStream.read();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#available()
+	 */
+	@Override
+	public int available() throws IOException {
+		return portletInputStream.available();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#close()
+	 */
+	@Override
+	public void close() throws IOException {
+		portletInputStream.close();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#mark(int)
+	 */
+	@Override
+	public synchronized void mark(int readlimit) {
+		portletInputStream.mark(readlimit);
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#markSupported()
+	 */
+	@Override
+	public boolean markSupported() {
+		return portletInputStream.markSupported();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#read(byte[], int, int)
+	 */
+	@Override
+	public int read(byte[] b, int off, int len) throws IOException {
+		return portletInputStream.read(b, off, len);
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#read(byte[])
+	 */
+	@Override
+	public int read(byte[] b) throws IOException {
+		return portletInputStream.read(b);
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#reset()
+	 */
+	@Override
+	public synchronized void reset() throws IOException {
+		portletInputStream.reset();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.InputStream#skip(long)
+	 */
+	@Override
+	public long skip(long n) throws IOException {
+		return portletInputStream.skip(n);
+	}
+	
+	/**
+	 * Get the wrapped {@link InputStream} instance.
+	 * @return The wrapped {@link InputStream} instance.
+	 */
+	public InputStream getInputStream() {
+		return portletInputStream;
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletOutputStream.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletOutputStream.java
new file mode 100644
index 0000000..871b3d2
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletOutputStream.java
@@ -0,0 +1,89 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletOutputStream;
+
+/**
+ * Wrapper object exposing a {@link OutputStream} from a portlet as a {@link ServletOutputStream} instance.
+ * Clients accessing this stream object will in fact operate on the
+ * {@link OutputStream} object wrapped by this stream object.
+ */
+public class PortletServletOutputStream extends ServletOutputStream {
+
+	private OutputStream portletOutputStream;
+	
+	public PortletServletOutputStream(OutputStream portletOutputStream) {
+		this.portletOutputStream = portletOutputStream;
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.OutputStream#write(int)
+	 */
+	@Override
+	public void write(int ch) throws IOException {
+		portletOutputStream.write(ch);
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.OutputStream#close()
+	 */
+	@Override
+	public void close() throws IOException {
+		portletOutputStream.close();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.OutputStream#flush()
+	 */
+	@Override
+	public void flush() throws IOException {
+		portletOutputStream.flush();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.OutputStream#write(byte[])
+	 */
+	@Override
+	public void write(byte[] b) throws IOException {
+		portletOutputStream.write(b);
+	}
+
+	/* (non-Javadoc)
+	 * @see java.io.OutputStream#write(byte[], int, int)
+	 */
+	@Override
+	public void write(byte[] b, int off, int len) throws IOException {
+		portletOutputStream.write(b, off, len);
+	}
+	
+	/**
+	 * Get the wrapped {@link OutputStream} instance.
+	 * @return The wrapped {@link OutputStream} instance.
+	 */
+	public OutputStream getOutputStream() {
+		return portletOutputStream;
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequest.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequest.java
new file mode 100644
index 0000000..b0a7303
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequest.java
@@ -0,0 +1,679 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletSession;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.context.PortletActionContext;
+
+import com.opensymphony.xwork2.inject.Inject;
+
+/**
+ * Wrapper object exposing a {@link PortletRequest} as a
+ * {@link HttpServletRequest} instance. Clients accessing this request object
+ * will in fact operate on the {@link PortletRequest} object wrapped by this
+ * request object.
+ */
+public class PortletServletRequest implements HttpServletRequest, PortletActionConstants {
+
+	private PortletRequest portletRequest;
+
+	private PortletContext portletContext;
+
+	private String extension;
+
+	public PortletServletRequest(PortletRequest portletRequest, PortletContext portletContext) {
+		this.portletRequest = portletRequest;
+		this.portletContext = portletContext;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getAuthType()
+	 */
+	public String getAuthType() {
+		return portletRequest.getAuthType();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getContextPath()
+	 */
+	public String getContextPath() {
+		return portletRequest.getContextPath();
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public Cookie[] getCookies() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getCookies();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public long getDateHeader(String name) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Gets a property from the {@link PortletRequest}. Note that a
+	 * {@link PortletRequest} is not guaranteed to map properties to headers.
+	 * 
+	 * @see PortletRequest#getProperty(String)
+	 * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+	 */
+	public String getHeader(String name) {
+		return portletRequest.getProperty(name);
+	}
+
+	/**
+	 * Gets the property names from the {@link PortletRequest}. Note that a
+	 * {@link PortletRequest} is not guaranteed to map properties to headers.
+	 * 
+	 * @see PortletRequest#getPropertyNames()
+	 * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+	 */
+	public Enumeration getHeaderNames() {
+		return portletRequest.getPropertyNames();
+	}
+
+	/**
+	 * Gets the values for the specified property from the
+	 * {@link PortletRequest}. Note that a {@link PortletRequest} is not
+	 * guaranteed to map properties to headers.
+	 * 
+	 * @see PortletRequest#getProperties(String)
+	 * @see HttpServletRequest#getHeaders(String)
+	 */
+	public Enumeration getHeaders(String name) {
+		return portletRequest.getProperties(name);
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public int getIntHeader(String name) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getMethod()
+	 */
+	public String getMethod() {
+		return null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+	 */
+	public String getPathInfo() {
+		return null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+	 */
+	public String getPathTranslated() {
+		return null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getQueryString()
+	 */
+	public String getQueryString() {
+		return null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+	 */
+	public String getRemoteUser() {
+		return portletRequest.getRemoteUser();
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getRequestURI() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public StringBuffer getRequestURL() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+	 */
+	public String getRequestedSessionId() {
+		return portletRequest.getRequestedSessionId();
+	}
+
+	/**
+	 * A {@link PortletRequest} has no servlet path. But for compatibility with
+	 * Struts 2 components and interceptors, the action parameter on the request
+	 * is mapped to the servlet path.
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getServletPath()
+	 */
+	public String getServletPath() {
+		String actionPath = getParameter(ACTION_PARAM);
+		if (!hasExtension(actionPath)) {
+			actionPath += "." + extension;
+		}
+		return actionPath;
+	}
+
+	private boolean hasExtension(String actionPath) {
+		return extension == null || "".equals(extension)
+				|| (actionPath != null && actionPath.endsWith("." + extension));
+	}
+
+	/**
+	 * Get the {@link PortletSession} as a {@link PortletHttpSession} instance.
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getSession()
+	 */
+	public HttpSession getSession() {
+		return new PortletHttpSession(portletRequest.getPortletSession());
+	}
+
+	/**
+	 * Get the {@link PortletSession} as a {@link PortletHttpSession} instance.
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+	 */
+	public HttpSession getSession(boolean create) {
+		return new PortletHttpSession(portletRequest.getPortletSession(create));
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+	 */
+	public Principal getUserPrincipal() {
+		return portletRequest.getUserPrincipal();
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public boolean isRequestedSessionIdFromCookie() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public boolean isRequestedSessionIdFromURL() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public boolean isRequestedSessionIdFromUrl() {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+	 */
+	public boolean isRequestedSessionIdValid() {
+		return portletRequest.isRequestedSessionIdValid();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+	 */
+	public boolean isUserInRole(String role) {
+		return portletRequest.isUserInRole(role);
+	}
+
+	/**
+	 * Gets an attribute value on the {@link PortletRequest}. If the attribute
+	 * name is <tt>javax.servlet.include.servlet_path</tt>, it returns the
+	 * same as {@link PortletServletRequest#getServletPath()}
+	 * 
+	 * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+	 */
+	public Object getAttribute(String name) {
+		if ("javax.servlet.include.servlet_path".equals(name)) {
+			return getServletPath();
+		} else {
+			return portletRequest.getAttribute(name);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getAttributeNames()
+	 */
+	public Enumeration getAttributeNames() {
+		return portletRequest.getAttributeNames();
+	}
+
+	/**
+	 * Can only be invoked in the event phase.
+	 * 
+	 * @see ServletRequest#getCharacterEncoding()
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public String getCharacterEncoding() {
+		if (portletRequest instanceof ActionRequest) {
+			return ((ActionRequest) portletRequest).getCharacterEncoding();
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/**
+	 * Can only be invoked in the event phase.
+	 * 
+	 * @see ServletRequest#getContentLength()
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public int getContentLength() {
+		if (portletRequest instanceof ActionRequest) {
+			return ((ActionRequest) portletRequest).getContentLength();
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/**
+	 * Can only be invoked in the event phase.
+	 * 
+	 * @see ServletRequest#getContentType()
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public String getContentType() {
+		if (portletRequest instanceof ActionRequest) {
+			return ((ActionRequest) portletRequest).getContentType();
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/**
+	 * Can only be invoked in the event phase. When invoked in the event phase,
+	 * it will wrap the portlet's {@link InputStream} as a
+	 * {@link PortletServletInputStream}.
+	 * 
+	 * @see ServletRequest#getInputStream()
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public ServletInputStream getInputStream() throws IOException {
+		if (portletRequest instanceof ActionRequest) {
+			return new PortletServletInputStream(((ActionRequest) portletRequest).getPortletInputStream());
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getLocalAddr() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getLocalAddr();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getLocalName() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getLocalName();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public int getLocalPort() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getLocalPort();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getLocale()
+	 */
+	public Locale getLocale() {
+		return portletRequest.getLocale();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getLocales()
+	 */
+	public Enumeration getLocales() {
+		return portletRequest.getLocales();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+	 */
+	public String getParameter(String name) {
+		return portletRequest.getParameter(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getParameterMap()
+	 */
+	public Map getParameterMap() {
+		return portletRequest.getParameterMap();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getParameterNames()
+	 */
+	public Enumeration getParameterNames() {
+		return portletRequest.getParameterNames();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+	 */
+	public String[] getParameterValues(String name) {
+		return portletRequest.getParameterValues(name);
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getProtocol() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getProtocol();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Can only be invoked in the event phase.
+	 * 
+	 * @see ServletRequest#getReader()
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public BufferedReader getReader() throws IOException {
+		if (portletRequest instanceof ActionRequest) {
+			return ((ActionRequest) portletRequest).getReader();
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+	 */
+	public String getRealPath(String path) {
+		return portletContext.getRealPath(path);
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getRemoteAddr() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getRemoteAddr();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public String getRemoteHost() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getRemoteHost();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public int getRemotePort() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getRemotePort();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/**
+	 * Get the {@link PortletRequestDispatcher} as a
+	 * {@link PortletServletRequestDispatcher} instance.
+	 * 
+	 * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+	 */
+	public RequestDispatcher getRequestDispatcher(String path) {
+		return new PortletServletRequestDispatcher(portletContext.getRequestDispatcher(path));
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getScheme()
+	 */
+	public String getScheme() {
+		return portletRequest.getScheme();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#getServerName()
+	 */
+	public String getServerName() {
+		return portletRequest.getServerName();
+	}
+
+	/**
+	 * Not allowed in a portlet.
+	 * 
+	 * @throws IllegalStateException
+	 *             Not allowed in a portlet.
+	 */
+	public int getServerPort() {
+		if (portletRequest instanceof HttpServletRequest) {
+			return ((HttpServletRequest) portletRequest).getServerPort();
+		}
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#isSecure()
+	 */
+	public boolean isSecure() {
+		return portletRequest.isSecure();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+	 */
+	public void removeAttribute(String name) {
+		portletRequest.removeAttribute(name);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.ServletRequest#setAttribute(java.lang.String,
+	 *      java.lang.Object)
+	 */
+	public void setAttribute(String name, Object o) {
+		portletRequest.setAttribute(name, o);
+	}
+
+	/**
+	 * Can only be invoked in the event phase.
+	 * 
+	 * @see ServletRequest#setCharacterEncoding(String)
+	 * @throws IllegalStateException
+	 *             If the portlet is not in the event phase.
+	 */
+	public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
+		if (portletRequest instanceof ActionRequest) {
+			((ActionRequest) portletRequest).setCharacterEncoding(env);
+		} else {
+			throw new IllegalStateException("Not allowed in render phase");
+		}
+	}
+
+	/**
+	 * Get the wrapped {@link PortletRequest} instance.
+	 * 
+	 * @return The wrapped {@link PortletRequest} instance.
+	 */
+	public PortletRequest getPortletRequest() {
+		return portletRequest;
+	}
+
+	@Inject(StrutsConstants.STRUTS_ACTION_EXTENSION)
+	public void setExtension(String extension) {
+		if (extension != null) {
+			this.extension = extension.split(",")[0];
+		}
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequestDispatcher.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequestDispatcher.java
new file mode 100644
index 0000000..54238f9
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletRequestDispatcher.java
@@ -0,0 +1,71 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.IOException;
+
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class PortletServletRequestDispatcher implements RequestDispatcher {
+
+	private PortletRequestDispatcher portletRequestDispatcher;
+	
+	public PortletServletRequestDispatcher(PortletRequestDispatcher portletRequestDispatcher) {
+		this.portletRequestDispatcher = portletRequestDispatcher;
+	}
+
+	public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+		throw new IllegalStateException("Not allowed in a portlet");
+		
+	}
+
+	public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+		if(request instanceof PortletServletRequest && response instanceof PortletServletResponse) {
+			PortletRequest req = ((PortletServletRequest)request).getPortletRequest();
+			PortletResponse resp = ((PortletServletResponse)response).getPortletResponse();
+			if(req instanceof RenderRequest && resp instanceof RenderResponse) {
+				try {
+					portletRequestDispatcher.include((RenderRequest)req, (RenderResponse)resp);
+				}
+				catch(PortletException e) {
+					throw new ServletException(e);
+				}
+			}
+			else {
+				throw new IllegalStateException("Can only be invoked in the render phase");
+			}
+		}
+		else {
+			throw new IllegalStateException("Can only be invoked in a portlet");
+		}
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletResponse.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletResponse.java
new file mode 100644
index 0000000..7e4c12b
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/PortletServletResponse.java
@@ -0,0 +1,234 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderResponse;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+public class PortletServletResponse implements HttpServletResponse {
+
+	private PortletResponse portletResponse;
+	
+	public PortletServletResponse(PortletResponse portletResponse) {
+		this.portletResponse = portletResponse;
+	}
+	
+	public void addCookie(Cookie cookie) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void addDateHeader(String name, long date) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void addHeader(String name, String value) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void addIntHeader(String name, int value) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public boolean containsHeader(String name) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public String encodeRedirectURL(String url) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public String encodeRedirectUrl(String url) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public String encodeURL(String url) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public String encodeUrl(String url) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void sendError(int sc) throws IOException {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void sendError(int sc, String msg) throws IOException {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void sendRedirect(String location) throws IOException {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setDateHeader(String name, long date) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setHeader(String name, String value) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setIntHeader(String name, int value) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setStatus(int sc) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setStatus(int sc, String sm) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void flushBuffer() throws IOException {
+		if(portletResponse instanceof RenderResponse) {
+			((RenderResponse)portletResponse).flushBuffer();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public int getBufferSize() {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).getBufferSize();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public String getCharacterEncoding() {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).getCharacterEncoding();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public String getContentType() {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).getContentType();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public Locale getLocale() {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).getLocale();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public ServletOutputStream getOutputStream() throws IOException {
+		if(portletResponse instanceof RenderResponse) {
+			return new PortletServletOutputStream(((RenderResponse)portletResponse).getPortletOutputStream());
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public PrintWriter getWriter() throws IOException {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).getWriter();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public boolean isCommitted() {
+		if(portletResponse instanceof RenderResponse) {
+			return ((RenderResponse)portletResponse).isCommitted();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public void reset() {
+		if(portletResponse instanceof RenderResponse) {
+			((RenderResponse)portletResponse).reset();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public void resetBuffer() {
+		if(portletResponse instanceof RenderResponse) {
+			((RenderResponse)portletResponse).resetBuffer();
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public void setBufferSize(int size) {
+		if(portletResponse instanceof RenderResponse) {
+			((RenderResponse)portletResponse).setBufferSize(size);
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public void setCharacterEncoding(String charset) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setContentLength(int len) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public void setContentType(String type) {
+		if(portletResponse instanceof RenderResponse) {
+			((RenderResponse)portletResponse).setContentType(type);
+		}
+		else {
+			throw new IllegalStateException("Not allowed in event phase");
+		}
+	}
+
+	public void setLocale(Locale loc) {
+		throw new IllegalStateException("Not allowed in a portlet");
+	}
+
+	public PortletResponse getPortletResponse() {
+		return portletResponse;
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/package.html b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/package.html
new file mode 100644
index 0000000..81981fe
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/servlet/package.html
@@ -0,0 +1,27 @@
+<!--
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<body>
+	Portlet wrapper objects for some of the servlet api objects. The wrappers simply delegate to the 
+	underlying, corresponding portlet objects. The portlet wrapper objects makes it easier to reuse 
+	interceptors and components from struts2 core.
+</body>
\ No newline at end of file
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/util/PortletUrlHelper.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/util/PortletUrlHelper.java
new file mode 100644
index 0000000..2c80778
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/portlet/util/PortletUrlHelper.java
@@ -0,0 +1,300 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.util;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.lang.StringUtils;
+import org.apache.struts2.StrutsException;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.context.PortletActionContext;
+
+import javax.portlet.*;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * Helper class for creating Portlet URLs. Portlet URLs are fundamentally different from regular
+ * servlet URLs since they never target the application itself; all requests go through the portlet
+ * container and must therefore be programatically constructed using the
+ * {@link javax.portlet.RenderResponse#createActionURL()} and
+ * {@link javax.portlet.RenderResponse#createRenderURL()} APIs.
+ *
+ */
+public class PortletUrlHelper {
+    public static final String ENCODING = "UTF-8";
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortletUrlHelper.class);
+
+    /**
+     * Create a portlet URL with for the specified action and namespace.
+     *
+     * @param action The action the URL should invoke.
+     * @param namespace The namespace of the action to invoke.
+     * @param method The method of the action to invoke.
+     * @param params The parameters of the URL.
+     * @param type The type of the url, either <tt>action</tt> or <tt>render</tt>
+     * @param mode The PortletMode of the URL.
+     * @param state The WindowState of the URL.
+     * @return The URL String.
+     */
+    public static String buildUrl(String action, String namespace, String method, Map params,
+            String type, String mode, String state) {
+        return buildUrl(action, namespace, method, params, null, type, mode, state,
+                true, true);
+    }
+
+    /**
+     * Create a portlet URL with for the specified action and namespace.
+     *
+     * @see #buildUrl(String, String, Map, String, String, String)
+     */
+    public static String buildUrl(String action, String namespace, String method, Map params,
+            String scheme, String type, String portletMode, String windowState,
+            boolean includeContext, boolean encodeResult) {
+    	StringBuffer resultingAction = new StringBuffer();
+        RenderRequest request = PortletActionContext.getRenderRequest();
+        RenderResponse response = PortletActionContext.getRenderResponse();
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Creating url. Action = " + action + ", Namespace = "
+                + namespace + ", Type = " + type);
+        }
+        namespace = prependNamespace(namespace, portletMode);
+        if (StringUtils.isEmpty(portletMode)) {
+            portletMode = PortletActionContext.getRenderRequest().getPortletMode().toString();
+        }
+        String result = null;
+        int paramStartIndex = action.indexOf('?');
+        if (paramStartIndex > 0) {
+            String value = action;
+            action = value.substring(0, value.indexOf('?'));
+            String queryStr = value.substring(paramStartIndex + 1);
+            StringTokenizer tok = new StringTokenizer(queryStr, "&");
+            while (tok.hasMoreTokens()) {
+                String paramVal = tok.nextToken();
+                String key = paramVal.substring(0, paramVal.indexOf('='));
+                String val = paramVal.substring(paramVal.indexOf('=') + 1);
+                params.put(key, new String[] { val });
+            }
+        }
+        if (StringUtils.isNotEmpty(namespace)) {
+            resultingAction.append(namespace);
+            if(!action.startsWith("/") && !namespace.endsWith("/")) {
+                resultingAction.append("/");
+            }
+        }
+        resultingAction.append(action);
+        if(StringUtils.isNotEmpty(method)) {
+        	resultingAction.append("!").append(method);
+        }
+        if (LOG.isDebugEnabled()) LOG.debug("Resulting actionPath: " + resultingAction);
+        params.put(PortletActionConstants.ACTION_PARAM, new String[] { resultingAction.toString() });
+
+        PortletURL url = null;
+        if ("action".equalsIgnoreCase(type)) {
+            if (LOG.isDebugEnabled()) LOG.debug("Creating action url");
+            url = response.createActionURL();
+        } else {
+            if (LOG.isDebugEnabled()) LOG.debug("Creating render url");
+            url = response.createRenderURL();
+        }
+
+        params.put(PortletActionConstants.MODE_PARAM, portletMode);
+        url.setParameters(ensureParamsAreStringArrays(params));
+
+        if ("HTTPS".equalsIgnoreCase(scheme)) {
+            try {
+                url.setSecure(true);
+            } catch (PortletSecurityException e) {
+                LOG.error("Cannot set scheme to https", e);
+            }
+        }
+        try {
+            url.setPortletMode(getPortletMode(request, portletMode));
+            url.setWindowState(getWindowState(request, windowState));
+        } catch (Exception e) {
+            LOG.error("Unable to set mode or state:" + e.getMessage(), e);
+        }
+        result = url.toString();
+        // TEMP BUG-WORKAROUND FOR DOUBLE ESCAPING OF AMPERSAND
+        if(result.indexOf("&amp;") >= 0) {
+            result = result.replace("&amp;", "&");
+        }
+        return result;
+
+    }
+
+    /**
+     *
+     * Prepend the namespace configuration for the specified namespace and PortletMode.
+     *
+     * @param namespace The base namespace.
+     * @param portletMode The PortletMode.
+     *
+     * @return prepended namespace.
+     */
+    private static String prependNamespace(String namespace, String portletMode) {
+        StringBuffer sb = new StringBuffer();
+        PortletMode mode = PortletActionContext.getRenderRequest().getPortletMode();
+        if(StringUtils.isNotEmpty(portletMode)) {
+            mode = new PortletMode(portletMode);
+        }
+        String portletNamespace = PortletActionContext.getPortletNamespace();
+        String modeNamespace = (String)PortletActionContext.getModeNamespaceMap().get(mode);
+        if (LOG.isDebugEnabled()) LOG.debug("PortletNamespace: " + portletNamespace + ", modeNamespace: " + modeNamespace);
+        if(StringUtils.isNotEmpty(portletNamespace)) {
+            sb.append(portletNamespace);
+        }
+        if(StringUtils.isNotEmpty(modeNamespace)) {
+            if(!modeNamespace.startsWith("/")) {
+                sb.append("/");
+            }
+            sb.append(modeNamespace);
+        }
+        if(StringUtils.isNotEmpty(namespace)) {
+            if(!namespace.startsWith("/")) {
+                sb.append("/");
+            }
+            sb.append(namespace);
+        }
+        if (LOG.isDebugEnabled()) LOG.debug("Resulting namespace: " + sb);
+        return sb.toString();
+    }
+
+    /**
+     * Encode an url to a non Struts action resource, like stylesheet, image or
+     * servlet.
+     *
+     * @param value
+     * @return encoded url to non Struts action resources.
+     */
+    public static String buildResourceUrl(String value, Map<String, Object> params) {
+        StringBuffer sb = new StringBuffer();
+        // Relative URLs are not allowed in a portlet
+        if (!value.startsWith("/")) {
+            sb.append("/");
+        }
+        sb.append(value);
+        if(params != null && params.size() > 0) {
+            sb.append("?");
+            Iterator<Map.Entry<String, Object>> it = params.entrySet().iterator();
+            try {
+            while(it.hasNext()) {
+            	Map.Entry<String, Object> entry = it.next();
+
+                sb.append(URLEncoder.encode(entry.getKey(), ENCODING)).append("=");
+                sb.append(URLEncoder.encode(entry.getValue().toString(), ENCODING));
+                if(it.hasNext()) {
+                    sb.append("&");
+                }
+            }
+            } catch (UnsupportedEncodingException e) {
+                throw new StrutsException("Encoding "+ENCODING+" not found");
+            }
+        }
+        RenderResponse resp = PortletActionContext.getRenderResponse();
+        RenderRequest req = PortletActionContext.getRenderRequest();
+        return resp.encodeURL(req.getContextPath() + sb.toString());
+    }
+
+    /**
+     * Will ensure that all entries in <code>params</code> are String arrays,
+     * as requried by the setParameters on the PortletURL.
+     *
+     * @param params The parameters to the URL.
+     * @return A Map with all parameters as String arrays.
+     */
+    public static Map ensureParamsAreStringArrays(Map<String, Object> params) {
+        Map<String, String[]> result = null;
+        if (params != null) {
+            result = new LinkedHashMap<String, String[]>(params.size());
+            Iterator<Map.Entry<String, Object>> it = params.entrySet().iterator();
+            while (it.hasNext()) {
+            	Map.Entry<String, Object> entry = it.next();
+            	Object val = entry.getValue();
+                if (val instanceof String[]) {
+                    result.put(entry.getKey(), (String[])val);
+                } else {
+                    result.put(entry.getKey(), new String[] { val.toString() });
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Convert the given String to a WindowState object.
+     *
+     * @param portletReq The RenderRequest.
+     * @param windowState The WindowState as a String.
+     * @return The WindowState that mathces the <tt>windowState</tt> String, or if
+     * the String is blank, the current WindowState.
+     */
+    private static WindowState getWindowState(RenderRequest portletReq,
+            String windowState) {
+        WindowState state = portletReq.getWindowState();
+        if (StringUtils.isNotEmpty(windowState)) {
+            if ("maximized".equalsIgnoreCase(windowState)) {
+                state = WindowState.MAXIMIZED;
+            } else if ("normal".equalsIgnoreCase(windowState)) {
+                state = WindowState.NORMAL;
+            } else if ("minimized".equalsIgnoreCase(windowState)) {
+                state = WindowState.MINIMIZED;
+            }
+        }
+        if(state == null) {
+            state = WindowState.NORMAL;
+        }
+        return state;
+    }
+
+    /**
+     * Convert the given String to a PortletMode object.
+     *
+     * @param portletReq The RenderRequest.
+     * @param portletMode The PortletMode as a String.
+     * @return The PortletMode that mathces the <tt>portletMode</tt> String, or if
+     * the String is blank, the current PortletMode.
+     */
+    private static PortletMode getPortletMode(RenderRequest portletReq,
+            String portletMode) {
+        PortletMode mode = portletReq.getPortletMode();
+
+        if (StringUtils.isNotEmpty(portletMode)) {
+            if ("edit".equalsIgnoreCase(portletMode)) {
+                mode = PortletMode.EDIT;
+            } else if ("view".equalsIgnoreCase(portletMode)) {
+                mode = PortletMode.VIEW;
+            } else if ("help".equalsIgnoreCase(portletMode)) {
+                mode = PortletMode.HELP;
+            }
+        }
+        if(mode == null) {
+            mode = PortletMode.VIEW;
+        }
+        return mode;
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/views/freemarker/PortletFreemarkerResult.java b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/views/freemarker/PortletFreemarkerResult.java
new file mode 100644
index 0000000..1d27f79
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/java/org/apache/struts2/views/freemarker/PortletFreemarkerResult.java
@@ -0,0 +1,282 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.views.freemarker;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Locale;
+
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.dispatcher.StrutsResultSupport;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.context.PortletActionContext;
+import org.apache.struts2.views.util.ResourceUtil;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import freemarker.template.Configuration;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+/**
+ */
+public class PortletFreemarkerResult extends StrutsResultSupport implements PortletActionConstants{
+
+    private static final long serialVersionUID = -5570612389289887543L;
+
+    protected ActionInvocation invocation;
+
+    protected Configuration configuration;
+
+    protected ObjectWrapper wrapper;
+    protected FreemarkerManager freemarkerManager;
+
+    /*
+     * Struts results are constructed for each result execeution
+     *
+     * the current context is availible to subclasses via these protected fields
+     */
+    protected String location;
+
+    private String pContentType = "text/html";
+
+    public PortletFreemarkerResult() {
+        super();
+    }
+
+    public PortletFreemarkerResult(String location) {
+        super(location);
+    }
+    
+    @Inject
+    public void setFreemarkerManager(FreemarkerManager mgr) {
+        this.freemarkerManager = mgr;
+    }
+
+    public void setContentType(String aContentType) {
+        pContentType = aContentType;
+    }
+
+    /**
+     * allow parameterization of the contentType the default being text/html
+     */
+    public String getContentType() {
+        return pContentType;
+    }
+
+    /**
+     * Execute this result, using the specified template location. <p/>The
+     * template location has already been interoplated for any variable
+     * substitutions <p/>this method obtains the freemarker configuration and
+     * the object wrapper from the provided hooks. It them implements the
+     * template processing workflow by calling the hooks for preTemplateProcess
+     * and postTemplateProcess
+     */
+    public void doExecute(String location, ActionInvocation invocation)
+            throws IOException, TemplateException, PortletException {
+        if (PortletActionContext.isEvent()) {
+            executeActionResult(location, invocation);
+        } else if (PortletActionContext.isRender()) {
+            executeRenderResult(location, invocation);
+        }
+    }
+
+    /**
+     * @param location
+     * @param invocation
+     */
+    private void executeActionResult(String location,
+                                     ActionInvocation invocation) {
+        ActionResponse res = PortletActionContext.getActionResponse();
+        // View is rendered outside an action...uh oh...
+		invocation.getInvocationContext().getSession().put(RENDER_DIRECT_LOCATION, location);
+        res.setRenderParameter(PortletActionConstants.ACTION_PARAM, "freemarkerDirect");
+        res.setRenderParameter(PortletActionConstants.MODE_PARAM, PortletActionContext
+                .getRequest().getPortletMode().toString());
+
+    }
+
+    /**
+     * @param location
+     * @param invocation
+     * @throws TemplateException
+     * @throws IOException
+     * @throws TemplateModelException
+     */
+    private void executeRenderResult(String location,
+                                     ActionInvocation invocation) throws TemplateException, IOException,
+            TemplateModelException, PortletException {
+        this.location = location;
+        this.invocation = invocation;
+        this.configuration = getConfiguration();
+        this.wrapper = getObjectWrapper();
+
+        HttpServletRequest req = ServletActionContext.getRequest();
+
+        if (!location.startsWith("/")) {
+            String base = ResourceUtil.getResourceBase(req);
+            location = base + "/" + location;
+        }
+
+        Template template = configuration.getTemplate(location, deduceLocale());
+        TemplateModel model = createModel();
+        // Give subclasses a chance to hook into preprocessing
+        if (preTemplateProcess(template, model)) {
+            try {
+                // Process the template
+                PortletActionContext.getRenderResponse().setContentType(pContentType);
+                template.process(model, getWriter());
+            } finally {
+                // Give subclasses a chance to hook into postprocessing
+                postTemplateProcess(template, model);
+            }
+        }
+    }
+
+    /**
+     * This method is called from {@link #doExecute(String, ActionInvocation)}
+     * to obtain the FreeMarker configuration object that this result will use
+     * for template loading. This is a hook that allows you to custom-configure
+     * the configuration object in a subclass, or to fetch it from an IoC
+     * container. <p/><b>The default implementation obtains the configuration
+     * from the ConfigurationManager instance. </b>
+     */
+    protected Configuration getConfiguration() throws TemplateException {
+        return freemarkerManager.getConfiguration(
+                ServletActionContext.getServletContext());
+    }
+
+    /**
+     * This method is called from {@link #doExecute(String, ActionInvocation)}
+     * to obtain the FreeMarker object wrapper object that this result will use
+     * for adapting objects into template models. This is a hook that allows you
+     * to custom-configure the wrapper object in a subclass. <p/><b>The default
+     * implementation returns {@link Configuration#getObjectWrapper()}</b>
+     */
+    protected ObjectWrapper getObjectWrapper() {
+        return configuration.getObjectWrapper();
+    }
+
+    /**
+     * The default writer writes directly to the response writer.
+     */
+    protected Writer getWriter() throws IOException {
+        return PortletActionContext.getRenderResponse().getWriter();
+    }
+
+    /**
+     * Build the instance of the ScopesHashModel, including JspTagLib support
+     * <p/>Objects added to the model are <p/>
+     * <ul>
+     * <li>Application - servlet context attributes hash model
+     * <li>JspTaglibs - jsp tag lib factory model
+     * <li>Request - request attributes hash model
+     * <li>Session - session attributes hash model
+     * <li>request - the HttpServletRequst object for direct access
+     * <li>response - the HttpServletResponse object for direct access
+     * <li>stack - the OgnLValueStack instance for direct access
+     * <li>ognl - the instance of the OgnlTool
+     * <li>action - the action itself
+     * <li>exception - optional : the JSP or Servlet exception as per the
+     * servlet spec (for JSP Exception pages)
+     * <li>struts - instance of the StrutsUtil class
+     * </ul>
+     */
+    protected TemplateModel createModel() throws TemplateModelException {
+        ServletContext servletContext = ServletActionContext
+                .getServletContext();
+        HttpServletRequest request = ServletActionContext.getRequest();
+        HttpServletResponse response = ServletActionContext.getResponse();
+        ValueStack stack = ServletActionContext.getContext()
+                .getValueStack();
+        return freemarkerManager.buildTemplateModel(stack,
+                invocation.getAction(), servletContext, request, response,
+                wrapper);
+    }
+
+    /**
+     * Returns the locale used for the
+     * {@link Configuration#getTemplate(String, Locale)}call. The base
+     * implementation simply returns the locale setting of the configuration.
+     * Override this method to provide different behaviour,
+     */
+    protected Locale deduceLocale() {
+        return configuration.getLocale();
+    }
+
+    /**
+     * the default implementation of postTemplateProcess applies the contentType
+     * parameter
+     */
+    protected void postTemplateProcess(Template template, TemplateModel data)
+            throws IOException {
+    }
+
+    /**
+     * Called before the execution is passed to template.process(). This is a
+     * generic hook you might use in subclasses to perform a specific action
+     * before the template is processed. By default does nothing. A typical
+     * action to perform here is to inject application-specific objects into the
+     * model root
+     *
+     * @return true to process the template, false to suppress template
+     *         processing.
+     */
+    protected boolean preTemplateProcess(Template template, TemplateModel model)
+            throws IOException {
+        Object attrContentType = template.getCustomAttribute("content_type");
+
+        if (attrContentType != null) {
+            ServletActionContext.getResponse().setContentType(
+                    attrContentType.toString());
+        } else {
+            String contentType = getContentType();
+
+            if (contentType == null) {
+                contentType = "text/html";
+            }
+
+            String encoding = template.getEncoding();
+
+            if (encoding != null) {
+                contentType = contentType + "; charset=" + encoding;
+            }
+
+            ServletActionContext.getResponse().setContentType(contentType);
+        }
+
+        return true;
+    }
+}
+
diff --git a/plugins/struts2-portlet-plugin/src/main/resources/LICENSE.txt b/plugins/struts2-portlet-plugin/src/main/resources/LICENSE.txt
new file mode 100644
index 0000000..dd5b3a5
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/resources/LICENSE.txt
@@ -0,0 +1,174 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
diff --git a/plugins/struts2-portlet-plugin/src/main/resources/NOTICE.txt b/plugins/struts2-portlet-plugin/src/main/resources/NOTICE.txt
new file mode 100644
index 0000000..bfba90c
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/resources/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Struts
+Copyright 2000-2011 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
\ No newline at end of file
diff --git a/plugins/struts2-portlet-plugin/src/main/resources/struts-plugin.xml b/plugins/struts2-portlet-plugin/src/main/resources/struts-plugin.xml
new file mode 100644
index 0000000..29b99bc
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/main/resources/struts-plugin.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
+    "http://struts.apache.org/dtds/struts-2.0.dtd">
+    
+<struts>
+	
+	<bean type="org.apache.struts2.components.UrlRenderer" name="portlet" class="org.apache.struts2.components.PortletUrlRenderer"/>
+	
+	<constant name="struts.urlRenderer" value="portlet" />
+	<constant name="struts.portlet.useDispatcherServlet" value="false" />
+	<constant name="struts.portlet.dispatcherServletName" value="Struts2PortletDispatcherServlet" />
+	<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="false" />
+
+    <package name="struts-portlet-default" extends="struts-default">
+
+	   <result-types>
+			<result-type name="dispatcher" class="org.apache.struts2.portlet.result.PortletResult" default="true"/>
+            <result-type name="redirect" class="org.apache.struts2.portlet.result.PortletResult"/>
+		    <result-type name="redirectAction" class="org.apache.struts2.portlet.result.PortletActionRedirectResult"/>
+			<result-type name="freemarker" class="org.apache.struts2.views.freemarker.PortletFreemarkerResult"/>
+			<result-type name="velocity" class="org.apache.struts2.portlet.result.PortletVelocityResult"/>
+	   </result-types>
+       
+       <interceptors>
+        <interceptor name="portletAware" class="org.apache.struts2.portlet.interceptor.PortletAwareInterceptor"/>
+		<interceptor name="portletState" class="org.apache.struts2.portlet.interceptor.PortletStateInterceptor"/>
+		<!-- Default stack for operating in portlet environment -->
+        <interceptor-stack name="portletDefaultStack">
+			<interceptor-ref name="portletState"/>
+            <interceptor-ref name="portletAware"/>
+            <interceptor-ref name="defaultStack"/>
+        </interceptor-stack>
+        <!-- Extension of the default portlet stack which also includes the token interceptor --> 
+		<interceptor-stack name="portletDefaultStackWithToken">
+			<interceptor-ref name="portletState"/>
+			<interceptor-ref name="portletAware"/>
+			<interceptor-ref name="token"/>
+			<interceptor-ref name="defaultStack"/>
+		</interceptor-stack>
+		   
+       </interceptors>
+       
+       <default-interceptor-ref name="portletDefaultStack"/>
+		
+	   <action name="renderDirect" class="org.apache.struts2.portlet.dispatcher.DirectRenderFromEventAction">
+	   		<result name="success">${location}</result>
+	   </action>
+	   <action name="freemarkerDirect" class="org.apache.struts2.portlet.dispatcher.DirectRenderFromEventAction">
+	   		<result type="freemarker" name="success">${location}</result>
+	   </action>
+	   <action name="velocityDirect" class="org.apache.struts2.portlet.dispatcher.DirectRenderFromEventAction">
+	   		<result type="velocity" name="success">${location}</result>
+	   </action>
+	</package>
+</struts>
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java
new file mode 100644
index 0000000..a18dc15
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java
@@ -0,0 +1,237 @@
+/*

+ * $Id$

+ *

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

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

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

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

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

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

+ *

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

+ *

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

+ * software distributed under the License is distributed on an

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

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

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+package org.apache.struts2.components;

+

+import java.io.IOException;

+import java.io.StringWriter;

+import java.io.Writer;

+import java.util.HashMap;

+import java.util.Map;

+

+import javax.portlet.PortletMode;

+import javax.portlet.PortletURL;

+

+import org.apache.struts2.StrutsStatics;

+import org.apache.struts2.StrutsTestCase;

+import org.apache.struts2.portlet.PortletActionConstants;

+import org.apache.struts2.portlet.context.PortletActionContext;

+import org.apache.struts2.portlet.servlet.PortletServletRequest;

+import org.apache.struts2.portlet.servlet.PortletServletResponse;

+import org.easymock.EasyMock;

+import org.springframework.mock.web.portlet.MockPortalContext;

+import org.springframework.mock.web.portlet.MockPortletContext;

+import org.springframework.mock.web.portlet.MockPortletURL;

+import org.springframework.mock.web.portlet.MockRenderRequest;

+import org.springframework.mock.web.portlet.MockRenderResponse;

+

+import com.opensymphony.xwork2.ActionContext;

+import com.opensymphony.xwork2.mock.MockActionInvocation;

+import com.opensymphony.xwork2.mock.MockActionProxy;

+import com.opensymphony.xwork2.util.ValueStack;

+

+public class PortletUrlRendererTest extends StrutsTestCase {

+

+	PortletUrlRenderer renderer;

+	MockPortletURL renderUrl;

+	MockPortletURL actionUrl;

+	MockRenderRequest request;

+	MockRenderResponse response;

+	MockPortletContext context;

+	ActionContext ctx;

+	ValueStack stack;

+

+	public void setUp() throws Exception {

+		super.setUp();

+		renderer = new PortletUrlRenderer();

+		context = new MockPortletContext();

+		renderUrl = new MockPortletURL(

+				new MockPortalContext(), "render");

+		actionUrl = new MockPortletURL(

+				new MockPortalContext(), "action");

+		request = new MockRenderRequest();

+		response = new MockRenderResponse() {

+			@Override

+			public PortletURL createActionURL() {

+				return actionUrl;

+			}

+			@Override

+			public PortletURL createRenderURL() {

+				return renderUrl;

+			}

+		};

+

+		ctx = ActionContext.getContext();

+		ctx.put(PortletActionConstants.PHASE,

+				PortletActionConstants.RENDER_PHASE);

+		ctx.put(PortletActionConstants.REQUEST, request);

+		ctx.put(PortletActionConstants.RESPONSE, response);

+		ctx.put(StrutsStatics.STRUTS_PORTLET_CONTEXT, context);

+

+		Map<PortletMode, String> modeMap = new HashMap<PortletMode, String>();

+		modeMap.put(PortletMode.VIEW, "/view");

+		ctx.put(PortletActionConstants.MODE_NAMESPACE_MAP, modeMap);

+		stack = ctx.getValueStack();

+	}

+

+	/**

+	 * Ensure that the namespace of the current executing action is used when no

+	 * namespace is specified. (WW-1875)

+	 */

+	public void testShouldIncludeCurrentNamespaceIfNoNamespaceSpecifiedForRenderUrl()

+			throws Exception {

+		

+		URL url = new URL(stack, new PortletServletRequest(request, null),

+				new PortletServletResponse(response));

+

+		MockActionInvocation ai = new MockActionInvocation();

+		MockActionProxy ap = new MockActionProxy();

+		ap.setActionName("testAction");

+		ap.setNamespace("/current_namespace");

+		ai.setProxy(ap);

+		ai.setStack(stack);

+		ai.setAction(new Object());

+		ctx.setActionInvocation(ai);

+

+		StringWriter renderOutput = new StringWriter();

+		renderer.renderUrl(renderOutput, url.getUrlProvider());

+

+		String action = renderUrl

+				.getParameter(PortletActionConstants.ACTION_PARAM);

+		assertEquals("/view/current_namespace/testAction", action);

+	}

+

+	/**

+	 * Ensure that the namespace of the current executing action is used when no

+	 * namespace is specified. (WW-1875)

+	 */

+	public void testShouldIncludeCurrentNamespaceIfNoNamespaceSpecifiedForRenderFormUrl()

+			throws Exception {

+

+		Form form = new Form(stack, new PortletServletRequest(request, null),

+				new PortletServletResponse(response));

+

+		MockActionInvocation ai = new MockActionInvocation();

+		MockActionProxy ap = new MockActionProxy();

+		ap.setActionName("testAction");

+		ap.setNamespace("/current_namespace");

+		ai.setProxy(ap);

+		ai.setStack(stack);

+		ai.setAction(new Object());

+		ctx.setActionInvocation(ai);

+

+		renderer.renderFormUrl(form);

+

+		String action = actionUrl

+				.getParameter(PortletActionConstants.ACTION_PARAM);

+		assertEquals("/view/current_namespace/testAction", action);

+	}

+	

+	public void testShouldEvaluateActionAsOGNLExpression() throws Exception {

+		

+		TestObject obj = new TestObject();

+		obj.someProperty = "EvaluatedProperty";

+		stack.push(obj);

+		MockActionInvocation ai = new MockActionInvocation();

+		MockActionProxy ap = new MockActionProxy();

+		ap.setActionName("testAction");

+		ap.setNamespace("");

+		ai.setProxy(ap);

+		ai.setStack(stack);

+		ctx.setActionInvocation(ai);

+		

+		URL url = new URL(stack, new PortletServletRequest(request, null),

+				new PortletServletResponse(response));

+		url.setAction("%{someProperty}");

+		

+		StringWriter renderOutput = new StringWriter();

+		renderer.renderUrl(renderOutput, url.getUrlProvider());

+

+		String action = renderUrl

+				.getParameter(PortletActionConstants.ACTION_PARAM);

+		assertEquals("/view/EvaluatedProperty", action);

+		

+	}

+	

+	public void testShouldEvaluateAnchorAsOGNLExpression() throws Exception {

+		

+		TestObject obj = new TestObject();

+		obj.someProperty = "EvaluatedProperty";

+		stack.push(obj);

+		MockActionInvocation ai = new MockActionInvocation();

+		MockActionProxy ap = new MockActionProxy();

+		ap.setActionName("testAction");

+		ap.setNamespace("");

+		ai.setProxy(ap);

+		ai.setStack(stack);

+		ctx.setActionInvocation(ai);

+		

+		URL url = new URL(stack, new PortletServletRequest(request, null),

+				new PortletServletResponse(response));

+		url.setAnchor("%{someProperty}");

+		

+		StringWriter renderOutput = new StringWriter();

+		renderer.renderUrl(renderOutput, url.getUrlProvider());

+		assertTrue(renderOutput.toString().indexOf("#EvaluatedProperty") != -1);

+		

+	}

+	

+	public void testShouldPassThroughRenderUrlToServletUrlRendererIfNotPortletRequest() throws Exception {

+		UrlRenderer mockRenderer = EasyMock.createMock(UrlRenderer.class);

+		ActionContext.getContext().put(StrutsStatics.STRUTS_PORTLET_CONTEXT, null);

+		renderer.setServletRenderer(mockRenderer);

+		URL url = new URL(stack, new PortletServletRequest(request, null), new PortletServletResponse(response));

+		StringWriter renderOutput = new StringWriter();

+		mockRenderer.renderUrl(renderOutput, url.getUrlProvider());

+		EasyMock.replay(mockRenderer);

+		renderer.renderUrl(renderOutput, url.getUrlProvider());

+		EasyMock.verify(mockRenderer);

+	}

+	

+	public void testShouldPassThroughRenderFormUrlToServletUrlRendererIfNotPortletRequest() throws Exception {

+		UrlRenderer mockRenderer = EasyMock.createMock(UrlRenderer.class);

+		ActionContext.getContext().put(StrutsStatics.STRUTS_PORTLET_CONTEXT, null);

+		renderer.setServletRenderer(mockRenderer);

+		Form form = new Form(stack, new PortletServletRequest(request, null), new PortletServletResponse(response));

+		mockRenderer.renderFormUrl(form);

+		EasyMock.replay(mockRenderer);

+		renderer.renderFormUrl(form);

+		EasyMock.verify(mockRenderer);

+	}

+	

+	public void testShouldPassThroughRenderUrlToServletUrlRendererWhenPortletUrlTypeIsNone() throws Exception {

+		UrlRenderer mockRenderer = EasyMock.createMock(UrlRenderer.class);

+		renderer.setServletRenderer(mockRenderer);

+		URL url = new URL(stack, new PortletServletRequest(request, null), new PortletServletResponse(response));

+		url.setPortletUrlType("none");

+		StringWriter renderOutput = new StringWriter();

+		mockRenderer.renderUrl(renderOutput, url.getUrlProvider());

+		EasyMock.replay(mockRenderer);

+		renderer.renderUrl(renderOutput, url.getUrlProvider());

+		EasyMock.verify(mockRenderer);

+	}

+	

+	private final static class TestObject {

+		public String someProperty;

+	}

+	

+	

+}

diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletApplicationMapTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletApplicationMapTest.java
new file mode 100644
index 0000000..2e7a320
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletApplicationMapTest.java
@@ -0,0 +1,170 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortletContext;
+
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+import org.jmock.core.Constraint;
+
+/**
+ */
+public class PortletApplicationMapTest extends MockObjectTestCase {
+
+    Mock mockPortletContext;
+
+    PortletContext portletContext;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mockPortletContext = mock(PortletContext.class);
+        portletContext = (PortletContext) mockPortletContext.proxy();
+    }
+
+    public void testGetFromAttributes() {
+        mockPortletContext.stubs().method("getAttribute").with(eq("dummyKey"))
+                .will(returnValue("dummyValue"));
+
+        PortletApplicationMap map = new PortletApplicationMap(
+                (PortletContext) mockPortletContext.proxy());
+
+        assertEquals("dummyValue", map.get("dummyKey"));
+    }
+
+    public void testGetFromInitParameters() {
+        mockPortletContext.stubs().method("getAttribute").with(eq("dummyKey"));
+        mockPortletContext.stubs().method("getInitParameter").with(
+                eq("dummyKey")).will(returnValue("dummyValue"));
+
+        PortletApplicationMap map = new PortletApplicationMap(
+                (PortletContext) mockPortletContext.proxy());
+
+        assertEquals("dummyValue", map.get("dummyKey"));
+    }
+
+    public void testPut() {
+        mockPortletContext.expects(once()).method("setAttribute").with(
+                new Constraint[] { eq("dummyKey"), eq("dummyValue") });
+        mockPortletContext.expects(once()).method("getAttribute").with(
+                eq("dummyKey")).will(returnValue("dummyValue"));
+        PortletApplicationMap map = new PortletApplicationMap(portletContext);
+        Object val = map.put("dummyKey", "dummyValue");
+        assertEquals("dummyValue", val);
+    }
+
+    public void testRemove() {
+        mockPortletContext.expects(once()).method("getAttribute").with(
+                eq("dummyKey")).will(returnValue("dummyValue"));
+        mockPortletContext.expects(once()).method("removeAttribute").with(
+                eq("dummyKey"));
+        PortletApplicationMap map = new PortletApplicationMap(portletContext);
+        Object val = map.remove("dummyKey");
+        assertEquals("dummyValue", val);
+    }
+
+    public void testEntrySet() {
+
+        Enumeration names = new Enumeration() {
+
+            List keys = Arrays.asList(new Object[] { "key1", "key2" });
+
+            Iterator it = keys.iterator();
+
+            public boolean hasMoreElements() {
+                return it.hasNext();
+            }
+
+            public Object nextElement() {
+                return it.next();
+            }
+
+        };
+        Enumeration initParamNames = new Enumeration() {
+
+            List keys = Arrays.asList(new Object[] { "key3" });
+
+            Iterator it = keys.iterator();
+
+            public boolean hasMoreElements() {
+                return it.hasNext();
+            }
+
+            public Object nextElement() {
+                return it.next();
+            }
+
+        };
+
+        mockPortletContext.stubs().method("getAttributeNames").will(
+                returnValue(names));
+        mockPortletContext.stubs().method("getInitParameterNames").will(
+                returnValue(initParamNames));
+        mockPortletContext.stubs().method("getAttribute").with(eq("key1"))
+                .will(returnValue("value1"));
+        mockPortletContext.stubs().method("getAttribute").with(eq("key2"))
+                .will(returnValue("value2"));
+        mockPortletContext.stubs().method("getInitParameter").with(eq("key3"))
+                .will(returnValue("value3"));
+
+        PortletApplicationMap map = new PortletApplicationMap(portletContext);
+        Set entries = map.entrySet();
+
+        assertEquals(3, entries.size());
+        Iterator it = entries.iterator();
+        Map.Entry entry = (Map.Entry) it.next();
+        assertEquals("key2", entry.getKey());
+        assertEquals("value2", entry.getValue());
+        entry = (Map.Entry) it.next();
+        assertEquals("key1", entry.getKey());
+        assertEquals("value1", entry.getValue());
+        entry = (Map.Entry) it.next();
+        assertEquals("key3", entry.getKey());
+        assertEquals("value3", entry.getValue());
+
+    }
+
+    public void testClear() {
+
+        mockPortletContext.expects(once()).method("removeAttribute").with(eq("key1"));
+        mockPortletContext.expects(once()).method("removeAttribute").with(eq("key2"));
+
+        ArrayList dummy = new ArrayList();
+        dummy.add("key1");
+        dummy.add("key2");
+
+        mockPortletContext.expects(once()).method("getAttributeNames").will(
+                returnValue(Collections.enumeration(dummy)));
+
+        PortletApplicationMap map = new PortletApplicationMap(portletContext);
+        map.clear();
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java
new file mode 100644
index 0000000..3c72543
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java
@@ -0,0 +1,108 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortletRequest;
+
+import org.jmock.MockObjectTestCase;
+import org.springframework.mock.web.portlet.MockPortletRequest;
+
+
+/**
+ * PortletRequestMapTest. Insert description.
+ *
+ */
+public class PortletRequestMapTest extends MockObjectTestCase {
+
+    public void testGet() {
+    	PortletRequest request = new MockPortletRequest();
+    	request.setAttribute("testAttribute", "testValue");
+
+    	PortletRequestMap map = new PortletRequestMap(request);
+        String value = (String)map.get("testAttribute");
+        assertEquals("testValue", value);
+    }
+
+    public void testPut() {
+    	PortletRequest request = new MockPortletRequest();
+    	PortletRequestMap map = new PortletRequestMap(request);
+        Object obj = map.put("testAttribute", "testValue1");
+        
+        assertEquals(obj, "testValue1");
+        assertEquals("testValue1", request.getAttribute("testAttribute"));
+    }
+
+    public void testClear() {
+    	MockPortletRequest request = new MockPortletRequest();
+    	request.setAttribute("testAttribute1", "testValue1");
+    	request.setAttribute("testAttribute2", "testValue2");
+
+
+        PortletRequestMap map = new PortletRequestMap(request);
+        map.clear();
+
+        assertFalse(request.getAttributeNames().hasMoreElements());
+    }
+
+    public void testRemove() {
+        MockPortletRequest request = new MockPortletRequest();
+        request.setAttribute("testAttribute1", "testValue1");
+        
+        PortletRequestMap map = new PortletRequestMap(request);
+        assertEquals("testValue1", map.remove("testAttribute1"));
+        assertNull(request.getAttribute("testAttribute1"));
+    }
+
+    public void testEntrySet() {
+    	MockPortletRequest request = new MockPortletRequest();
+    	request.setAttribute("testAttribute1", "testValue1");
+    	request.setAttribute("testAttribute2", "testValue2");
+
+        PortletRequestMap map = new PortletRequestMap(request);
+        Set entries = map.entrySet();
+
+        assertEquals(2, entries.size());
+        Iterator it = entries.iterator();
+        Map.Entry entry = (Map.Entry)it.next();
+        checkEntry(entry);
+        entry = (Map.Entry)it.next();
+        checkEntry(entry);
+
+    }
+    
+	private void checkEntry(Map.Entry entry) {
+		if(entry.getKey().equals("testAttribute1")) {
+        	assertEquals("testValue1", entry.getValue());
+        }
+        else if(entry.getKey().equals("testAttribute2")) {
+        	assertEquals("testValue2", entry.getValue());
+        }
+        else {
+        	fail("Unexpected entry in etry set: " + entry);
+        }
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java
new file mode 100644
index 0000000..2c88256
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java
@@ -0,0 +1,125 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortletSession;
+
+import junit.framework.TestCase;
+
+import org.springframework.mock.web.portlet.MockPortletRequest;
+
+
+/**
+ * PortletSessionMapTest. Insert description.
+ *
+ */
+public class PortletSessionMapTest extends TestCase {
+
+    public void testPut() {
+
+    	MockPortletRequest request = new MockPortletRequest();
+
+        PortletSessionMap map = new PortletSessionMap(request);
+        assertEquals("testValue1", map.put("testAttribute1", "testValue1"));
+        assertEquals("testValue2", map.put("testAttribute2", "testValue2"));
+
+        PortletSession session = request.getPortletSession();
+        // Assert that the values has been propagated to the session
+        assertEquals("testValue1", session.getAttribute("testAttribute1"));
+        assertEquals("testValue2", session.getAttribute("testAttribute2"));
+    }
+
+    public void testGet() {
+    	MockPortletRequest request = new MockPortletRequest();
+    	PortletSession session = request.getPortletSession();
+    	session.setAttribute("testAttribute1", "testValue1");
+    	session.setAttribute("testAttribute2", "testValue2");
+        PortletSessionMap map = new PortletSessionMap(request);
+        Object val1 = map.get("testAttribute1");
+        Object val2 = map.get("testAttribute2");
+        // Assert that the values from the session is in the map
+        assertEquals("testValue1", val1);
+        assertEquals("testValue2", val2);
+    }
+
+    public void testClear() {
+        MockPortletRequest req = new MockPortletRequest();
+        PortletSession session = req.getPortletSession();
+    	session.setAttribute("testAttribute1", "testValue1");
+    	session.setAttribute("testAttribute2", "testValue2");
+        
+        PortletSessionMap map = new PortletSessionMap(req);
+        map.clear();
+        
+        // Assert that there are no elements in the portlet session
+        assertFalse(req.getPortletSession().getAttributeNames().hasMoreElements());
+    }
+
+    public void testRemove() {
+    	MockPortletRequest request = new MockPortletRequest();
+    	PortletSession session = request.getPortletSession();
+    	session.setAttribute("testAttribute1", "testValue1");
+
+        PortletSessionMap map = new PortletSessionMap(request);
+        Object ret = map.remove("testAttribute1");
+        // Assert that the element that was removed was returned and the key is no longer in the
+        // portlet session
+        assertEquals("testValue1", ret);
+        assertNull(session.getAttribute("testAttribute1"));
+    }
+
+    public void testEntrySet() {
+    	MockPortletRequest request = new MockPortletRequest();
+    	PortletSession session = request.getPortletSession();
+    	session.setAttribute("testAttribute1", "testValue1");
+    	session.setAttribute("testAttribute2", "testValue2");
+
+        PortletSessionMap map = new PortletSessionMap(request);
+        Set entries = map.entrySet();
+
+        assertEquals(2, entries.size());
+        Iterator it = entries.iterator();
+        Map.Entry entry = (Map.Entry)it.next();
+        checkEntry(entry);
+        entry = (Map.Entry)it.next();
+        checkEntry(entry);
+
+    }
+
+	private void checkEntry(Map.Entry entry) {
+		if(entry.getKey().equals("testAttribute1")) {
+        	assertEquals("testValue1", entry.getValue());
+        }
+        else if(entry.getKey().equals("testAttribute2")) {
+        	assertEquals("testValue2", entry.getValue());
+        }
+        else {
+        	fail("Unexpected entry in etry set: " + entry);
+        }
+	}
+
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/context/PortletActionContextTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/context/PortletActionContextTest.java
new file mode 100644
index 0000000..25c9553
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/context/PortletActionContextTest.java
@@ -0,0 +1,184 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.context;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import junit.textui.TestRunner;
+
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+import com.opensymphony.xwork2.ActionContext;
+
+/**
+ */
+public class PortletActionContextTest extends MockObjectTestCase {
+
+    Mock mockRenderRequest;
+    Mock mockRenderResponse;
+    Mock mockPortletConfig;
+    Mock mockActionRequest;
+    Mock mockActionResponse;
+
+    RenderRequest renderRequest;
+    RenderResponse renderResponse;
+
+    ActionRequest actionRequest;
+    ActionResponse actionResponse;
+
+    PortletConfig portletConfig;
+
+    Map context = new HashMap();
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mockRenderRequest = mock(RenderRequest.class);
+        mockRenderResponse = mock(RenderResponse.class);
+        mockActionRequest = mock(ActionRequest.class);
+        mockActionResponse = mock(ActionResponse.class);
+        mockPortletConfig = mock(PortletConfig.class);
+
+        renderRequest = (RenderRequest)mockRenderRequest.proxy();
+        renderResponse = (RenderResponse)mockRenderResponse.proxy();
+        actionRequest = (ActionRequest)mockActionRequest.proxy();
+        actionResponse = (ActionResponse)mockActionResponse.proxy();
+        portletConfig = (PortletConfig)mockPortletConfig.proxy();
+
+
+        ActionContext.setContext(new ActionContext(context));
+    }
+
+    public void testGetPhase() {
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+
+        assertEquals(PortletActionConstants.RENDER_PHASE, PortletActionContext.getPhase());
+    }
+
+    public void testIsRender() {
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+
+        assertTrue(PortletActionContext.isRender());
+        assertFalse(PortletActionContext.isEvent());
+    }
+
+    public void testIsEvent() {
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+
+        assertTrue(PortletActionContext.isEvent());
+        assertFalse(PortletActionContext.isRender());
+    }
+
+    public void testGetPortletConfig() {
+        context.put(PortletActionConstants.PORTLET_CONFIG, portletConfig);
+        assertSame(portletConfig, PortletActionContext.getPortletConfig());
+    }
+
+    public void testGetRenderRequestAndResponse() {
+        context.put(PortletActionConstants.REQUEST, renderRequest);
+        context.put(PortletActionConstants.RESPONSE, renderResponse);
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+        assertSame(renderRequest, PortletActionContext.getRenderRequest());
+        assertSame(renderResponse, PortletActionContext.getRenderResponse());
+        assertSame(renderRequest, PortletActionContext.getRequest());
+        assertSame(renderResponse, PortletActionContext.getResponse());
+    }
+
+    public void testGetRenderRequestAndResponseInEventPhase() {
+        context.put(PortletActionConstants.REQUEST, renderRequest);
+        context.put(PortletActionConstants.RESPONSE, renderResponse);
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+        try {
+            PortletActionContext.getRenderRequest();
+            fail("Should throw IllegalStateException!");
+        }
+        catch(IllegalStateException e) {
+            assertTrue(true);
+        }
+        try {
+            PortletActionContext.getRenderResponse();
+            fail("Should throw IllegalStateException!");
+        }
+        catch(IllegalStateException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGetActionRequestAndResponse() {
+        context.put(PortletActionConstants.REQUEST, actionRequest);
+        context.put(PortletActionConstants.RESPONSE, actionResponse);
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+        assertSame(actionRequest, PortletActionContext.getActionRequest());
+        assertSame(actionResponse, PortletActionContext.getActionResponse());
+        assertSame(actionRequest, PortletActionContext.getRequest());
+        assertSame(actionResponse, PortletActionContext.getResponse());
+    }
+
+    public void testGetActionRequestAndResponseInRenderPhase() {
+        context.put(PortletActionConstants.REQUEST, actionRequest);
+        context.put(PortletActionConstants.RESPONSE, actionResponse);
+        context.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+        try {
+            PortletActionContext.getActionRequest();
+            fail("Should throw IllegalStateException!");
+        }
+        catch(IllegalStateException e) {
+            assertTrue(true);
+        }
+        try {
+            PortletActionContext.getActionResponse();
+            fail("Should throw IllegalStateException!");
+        }
+        catch(IllegalStateException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGetNamespace() {
+        context.put(PortletActionConstants.PORTLET_NAMESPACE, "testNamespace");
+        assertEquals("testNamespace", PortletActionContext.getPortletNamespace());
+    }
+
+    public void testGetDefaultActionForMode() {
+        ActionMapping mapping = new ActionMapping();
+        context.put(PortletActionConstants.DEFAULT_ACTION_FOR_MODE, mapping);
+        assertEquals(mapping, PortletActionContext.getDefaultActionForMode());
+    }
+
+    public void tearDown() throws Exception {
+        ActionContext.setContext(null);
+        super.tearDown();
+    }
+
+    public static void main(String[] args) {
+        TestRunner.run(PortletActionContextTest.class);
+    }
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java
new file mode 100644
index 0000000..2359573
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java
@@ -0,0 +1,299 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.dispatcher;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.ActionProxyFactory;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.easymock.EasyMock;
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+import org.jmock.core.Constraint;
+import org.springframework.mock.web.portlet.MockPortletConfig;
+import org.springframework.mock.web.portlet.MockPortletContext;
+
+import javax.portlet.*;
+import java.util.*;
+
+/**
+ * Jsr168DispatcherTest. Insert description.
+ * 
+ */
+public class Jsr168DispatcherTest extends MockObjectTestCase implements PortletActionConstants {
+
+	private final String MULTIPART_REQUEST = "-----------------------------4827543632391\r\n"
+			+ "Content-Disposition: form-data; name=\"upload\"; filename=\"test.txt\"\r\n"
+			+ "Content-Type: text/plain\r\n" 
+			+ "\r\n" 
+			+ "This is a test file\r\n"
+			+ "-----------------------------4827543632391\r\n" 
+			+ "Content-Disposition: form-data; name=\"caption\"\r\n"
+			+ "\r\n" 
+			+ "TestCaption\r\n" + "-----------------------------4827543632391--";
+
+	Jsr168Dispatcher dispatcher = null;
+
+	Mock mockConfig = null;
+
+	Mock mockCtx = null;
+
+	Mock mockRequest = null;
+
+	Mock mockSession = null;
+
+	Mock mockActionFactory = null;
+
+	Mock mockActionProxy = null;
+
+	Mock mockAction = null;
+
+	Mock mockInvocation = null;
+
+	public void setUp() throws Exception {
+		super.setUp();
+		dispatcher = new Jsr168Dispatcher();
+	}
+
+	private void initPortletConfig(final Map<String, String> initParams, final Map<String, Object> attributes) {
+		mockConfig = mock(PortletConfig.class);
+		mockCtx = mock(PortletContext.class);
+		mockConfig.stubs().method(ANYTHING);
+		mockCtx.stubs().method(ANYTHING);
+		setupStub(initParams, mockConfig, "getInitParameter");
+		mockCtx.stubs().method("getAttributeNames").will(returnValue(Collections.enumeration(attributes.keySet())));
+		setupStub(attributes, mockCtx, "getAttribute");
+		mockConfig.stubs().method("getPortletContext").will(returnValue(mockCtx.proxy()));
+		mockCtx.stubs().method("getInitParameterNames").will(returnValue(Collections.enumeration(initParams.keySet())));
+		setupStub(initParams, mockCtx, "getInitParameter");
+		mockConfig.stubs().method("getInitParameterNames").will(
+				returnValue(Collections.enumeration(initParams.keySet())));
+		setupStub(initParams, mockConfig, "getInitParameter");
+		mockConfig.stubs().method("getResourceBundle").will(returnValue(new ListResourceBundle() {
+			protected Object[][] getContents() {
+				return new String[][] { { "javax.portlet.title", "MyTitle" } };
+			}
+		}));
+	}
+
+	private void setupActionFactory(String namespace, String actionName, String result, ValueStack stack) {
+		if (mockActionFactory == null) {
+			mockActionFactory = mock(ActionProxyFactory.class);
+		}
+		mockAction = mock(Action.class);
+		mockActionProxy = mock(ActionProxy.class);
+		mockInvocation = mock(ActionInvocation.class);
+
+		mockActionFactory.expects(once()).method("createActionProxy").with(
+				new Constraint[] { eq(namespace), eq(actionName), NULL, isA(Map.class) }).will(
+				returnValue(mockActionProxy.proxy()));
+		mockActionProxy.stubs().method("getAction").will(returnValue(mockAction.proxy()));
+		mockActionProxy.expects(once()).method("execute").will(returnValue(result));
+		mockActionProxy.expects(once()).method("getInvocation").will(returnValue(mockInvocation.proxy()));
+		mockInvocation.stubs().method("getStack").will(returnValue(stack));
+
+	}
+
+	public void testParseConfigWithBang() {
+		MockPortletContext portletContext = new MockPortletContext();
+		MockPortletConfig portletConfig = new MockPortletConfig(portletContext);
+
+		portletConfig.addInitParameter("viewNamespace", "/view");
+		portletConfig.addInitParameter("defaultViewAction", "index!input");
+
+		Map<PortletMode, ActionMapping> actionMap = new HashMap<PortletMode, ActionMapping>();
+
+		dispatcher.parseModeConfig(actionMap, portletConfig, PortletMode.VIEW, "viewNamespace", "defaultViewAction");
+
+		ActionMapping mapping = actionMap.get(PortletMode.VIEW);
+		assertEquals("index", mapping.getName());
+		assertEquals("/view", mapping.getNamespace());
+		assertEquals("input", mapping.getMethod());
+	}
+
+	public void testRender_ok() throws Exception {
+		final Mock mockResponse = mock(RenderResponse.class);
+		mockResponse.stubs().method(ANYTHING);
+
+		PortletMode mode = PortletMode.VIEW;
+
+		Map<String, String[]> requestParams = new HashMap<String, String[]>();
+		requestParams.put(PortletActionConstants.ACTION_PARAM, new String[] { "/view/testAction" });
+		requestParams.put(EVENT_ACTION, new String[] { "true" });
+		requestParams.put(PortletActionConstants.MODE_PARAM, new String[] { mode.toString() });
+
+		Map<String, Object> sessionMap = new HashMap<String, Object>();
+
+		Map<String, String> initParams = new HashMap<String, String>();
+		initParams.put("viewNamespace", "/view");
+		initParams.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, "true");
+
+		initPortletConfig(initParams, new HashMap<String, Object>());
+		initRequest(requestParams, new HashMap<String, Object>(), sessionMap, new HashMap<String, String[]>(), PortletMode.VIEW, WindowState.NORMAL,
+				false, null);
+		setupActionFactory("/view", "testAction", "success", EasyMock.createNiceMock(ValueStack.class));
+
+		mockInvocation.expects(once()).method("getStack").will(returnValue(null));
+		// mockSession.expects(once()).method("setAttribute").with(new
+		// Constraint[]{eq(PortletActionConstants.LAST_MODE),
+		// eq(PortletMode.VIEW)});
+		dispatcher.setActionProxyFactory((ActionProxyFactory) mockActionFactory.proxy());
+		dispatcher.init((PortletConfig) mockConfig.proxy());
+		dispatcher.render((RenderRequest) mockRequest.proxy(), (RenderResponse) mockResponse.proxy());
+	}
+
+	public void testProcessAction_ok() throws Exception {
+		final Mock mockResponse = mock(ActionResponse.class);
+
+		PortletMode mode = PortletMode.VIEW;
+		Map<String, String> initParams = new HashMap<String, String>();
+		initParams.put("viewNamespace", "/view");
+
+		Map<String, String[]> requestParams = new HashMap<String, String[]>();
+		requestParams.put(PortletActionConstants.ACTION_PARAM, new String[] { "/view/testAction" });
+		requestParams.put(PortletActionConstants.MODE_PARAM, new String[] { mode.toString() });
+
+		initParams.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, "true");
+		initPortletConfig(initParams, new HashMap<String, Object>());
+		initRequest(requestParams, new HashMap<String, Object>(), new HashMap<String, Object>(), new HashMap<String, String[]>(), PortletMode.VIEW, WindowState.NORMAL,
+				true, null);
+		setupActionFactory("/view", "testAction", "success", EasyMock.createNiceMock(ValueStack.class));
+		// mockSession.expects(once()).method("setAttribute").with(new
+		// Constraint[]{eq(PortletActionConstants.LAST_MODE),
+		// eq(PortletMode.VIEW)});
+		dispatcher.setActionProxyFactory((ActionProxyFactory) mockActionFactory.proxy());
+		dispatcher.init((PortletConfig) mockConfig.proxy());
+		dispatcher.processAction((ActionRequest) mockRequest.proxy(), (ActionResponse) mockResponse.proxy());
+	}
+
+	/**
+	 * Initialize the mock request (and as a result, the mock session)
+	 * 
+	 * @param requestParams
+	 *            The request parameters
+	 * @param requestAttributes
+	 *            The request attributes
+	 * @param sessionParams
+	 *            The session attributes
+	 * @param renderParams
+	 *            The render parameters. Will only be set if
+	 *            <code>isEvent</code> is <code>true</code>
+	 * @param mode
+	 *            The portlet mode
+	 * @param state
+	 *            The portlet window state
+	 * @param isEvent
+	 *            <code>true</code> when the request is an ActionRequest.
+	 * @param locale
+	 *            The locale. If <code>null</code>, the request will return
+	 *            <code>Locale.getDefault()</code>
+	 */
+	private void initRequest(Map<String, String[]> requestParams, Map<String, Object> requestAttributes, Map<String, Object> sessionParams, Map<String, String[]> renderParams,
+			PortletMode mode, WindowState state, boolean isEvent, Locale locale) {
+		mockRequest = isEvent ? mock(ActionRequest.class) : mock(RenderRequest.class);
+		mockSession = mock(PortletSession.class);
+		mockSession.stubs().method(ANYTHING);
+		mockRequest.stubs().method(ANYTHING);
+		setupStub(sessionParams, mockSession, "getAttribute");
+		mockSession.stubs().method("getAttributeNames").will(
+				returnValue(Collections.enumeration(sessionParams.keySet())));
+		setupParamStub(requestParams, mockRequest, "getParameter");
+		setupStub(requestAttributes, mockRequest, "getAttribute");
+		mockRequest.stubs().method("getAttributeNames").will(
+				returnValue(Collections.enumeration(requestAttributes.keySet())));
+		mockRequest.stubs().method("getParameterMap").will(returnValue(requestParams));
+		mockRequest.stubs().method("getParameterNames").will(
+				returnValue(Collections.enumeration(requestParams.keySet())));
+		mockRequest.stubs().method("getPortletSession").will(returnValue(mockSession.proxy()));
+		if (locale != null) {
+			mockRequest.stubs().method("getLocale").will(returnValue(locale));
+		} else {
+			mockRequest.stubs().method("getLocale").will(returnValue(Locale.getDefault()));
+		}
+		mockRequest.stubs().method("getPortletMode").will(returnValue(mode));
+		mockRequest.stubs().method("getWindowState").will(returnValue(state));
+	}
+
+	private void setupParamStub(Map<String, String[]> requestParams, Mock mockRequest, String method) {
+		Map<String, String> newMap = new HashMap<String, String>();
+        for ( String key : requestParams.keySet() ) {
+            String[] val = requestParams.get(key);
+            newMap.put(key, val[0]);
+        }
+		setupStub(newMap, mockRequest, method);
+
+	}
+
+	/**
+	 * Set up stubs for the mock.
+	 * 
+	 * @param map
+	 *            The map containing the <code>key</code> and
+	 *            <code>values</code>. The key is the expected parameter to
+	 *            <code>method</code>, and value is the value that should be
+	 *            returned from the stub.
+	 * @param mock
+	 *            The mock to initialize.
+	 * @param method
+	 *            The name of the method to stub.
+	 */
+	private void setupStub(Map map, Mock mock, String method) {
+        for ( Object key : map.keySet() ) {
+            Object val = map.get(key);
+            mock.stubs().method(method).with(eq(key)).will(returnValue(val));
+        }
+	}
+
+	public void testModeChangeUsingPortletWidgets() throws Exception {
+		final Mock mockResponse = mock(RenderResponse.class);
+		mockResponse.stubs().method(ANYTHING);
+		PortletMode mode = PortletMode.EDIT;
+
+		Map<String, String[]> requestParams = new HashMap<String, String[]>();
+		requestParams.put(PortletActionConstants.ACTION_PARAM, new String[] { "/view/testAction" });
+		requestParams.put(EVENT_ACTION, new String[] { "false" });
+		requestParams.put(PortletActionConstants.MODE_PARAM, new String[] { PortletMode.VIEW.toString() });
+
+		Map<String, Object> sessionMap = new HashMap<String, Object>();
+
+		Map<String, String> initParams = new HashMap<String, String>();
+		initParams.put("viewNamespace", "/view");
+		initParams.put("editNamespace", "/edit");
+
+		initPortletConfig(initParams, new HashMap<String, Object>());
+		initRequest(requestParams, new HashMap<String, Object>(), sessionMap, new HashMap<String, String[]>(), mode, WindowState.NORMAL, false, null);
+		setupActionFactory("/edit", "default", "success", EasyMock.createNiceMock(ValueStack.class));
+
+		mockInvocation.expects(once()).method("getStack").will(returnValue(null));
+		// mockSession.expects(once()).method("setAttribute").with(new
+		// Constraint[]{eq(PortletActionConstants.LAST_MODE),
+		// eq(PortletMode.VIEW)});
+		dispatcher.setActionProxyFactory((ActionProxyFactory) mockActionFactory.proxy());
+		dispatcher.init((PortletConfig) mockConfig.proxy());
+		dispatcher.render((RenderRequest) mockRequest.proxy(), (RenderResponse) mockResponse.proxy());
+	}
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptorTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptorTest.java
new file mode 100644
index 0000000..f059922
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletAwareInterceptorTest.java
@@ -0,0 +1,99 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletRequest;
+
+import junit.framework.TestCase;
+
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.easymock.EasyMock;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+
+public class PortletAwareInterceptorTest extends TestCase implements PortletActionConstants {
+
+	private PortletAwareInterceptor interceptor;
+	private TestAction action;
+	private PortletRequest portletRequest;
+	private PortletConfig portletConfig;
+	private Map<String, Object> contextMap;
+	private ActionInvocation invocation;
+	
+	protected void setUp() throws Exception {
+		super.setUp();
+		interceptor = new PortletAwareInterceptor();
+		action = new TestAction();
+		portletRequest = EasyMock.createNiceMock(PortletRequest.class);
+		portletConfig = EasyMock.createNiceMock(PortletConfig.class);
+		contextMap = new HashMap<String, Object>();
+		invocation = EasyMock.createNiceMock(ActionInvocation.class);
+		EasyMock.expect(invocation.getAction()).andReturn(action);
+		EasyMock.expect(invocation.getInvocationContext()).andReturn(new ActionContext(contextMap));
+	}
+	
+	protected void tearDown() throws Exception {
+		super.tearDown();
+	}
+	
+	public void testPortletRequestIsSet() throws Exception {
+		contextMap.put(REQUEST, portletRequest);
+		EasyMock.replay(invocation);
+		interceptor.intercept(invocation);
+		assertEquals(portletRequest, action.getPortletRequest());
+	}
+	
+	public void testPortletConfigIsSet() throws Exception {
+		contextMap.put(PORTLET_CONFIG, portletConfig);
+		EasyMock.replay(invocation);
+		interceptor.intercept(invocation);
+		assertEquals(portletConfig, action.getPortletConfig());
+	}
+	
+	public static class TestAction implements PortletRequestAware, PortletConfigAware {
+
+		private PortletRequest request;
+		private PortletConfig config;
+
+		public void setPortletRequest(PortletRequest request) {
+			this.request = request;
+		}
+
+		public void setPortletConfig(PortletConfig portletConfig) {
+			this.config = portletConfig;
+		}
+
+		public PortletConfig getPortletConfig() {
+			return config;
+		}
+
+		public PortletRequest getPortletRequest() {
+			return request;
+		}
+		
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptorTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptorTest.java
new file mode 100644
index 0000000..c584f8a
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/interceptor/PortletStateInterceptorTest.java
@@ -0,0 +1,160 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.interceptor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.ActionResponse;
+import javax.portlet.RenderRequest;
+
+import junit.framework.TestCase;
+
+import org.apache.struts2.StrutsTestCase;
+import org.apache.struts2.dispatcher.DefaultActionSupport;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.dispatcher.DirectRenderFromEventAction;
+import org.easymock.EasyMock;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+
+public class PortletStateInterceptorTest extends StrutsTestCase implements PortletActionConstants {
+
+	private PortletStateInterceptor interceptor;
+	
+	public void setUp() throws Exception {
+	    super.setUp();
+		interceptor = new PortletStateInterceptor();
+	}
+	
+	public void testCopyValueStackFromEventToRenderPhase() throws Exception {
+		ActionResponse actionResponse = EasyMock.createNiceMock(ActionResponse.class);
+		ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class);
+		
+		Map<String, Object> ctxMap = new HashMap<String, Object>();
+		ctxMap.put(PHASE, EVENT_PHASE);
+		ctxMap.put(RESPONSE, actionResponse);
+		Map<String, Object> session = new HashMap<String, Object>();
+		
+		ActionContext ctx = new ActionContext(ctxMap);
+		ctx.setSession(session);
+		EasyMock.expect(invocation.getInvocationContext()).andStubReturn(ctx);
+		actionResponse.setRenderParameter(EVENT_ACTION, "true");
+		
+		ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack();
+		EasyMock.expect(invocation.getStack()).andStubReturn(stack);
+		
+		EasyMock.replay(actionResponse);
+		EasyMock.replay(invocation);
+		
+		interceptor.intercept(invocation);
+		
+		EasyMock.verify(actionResponse);
+		EasyMock.verify(invocation);
+		
+		assertSame(stack, session.get(STACK_FROM_EVENT_PHASE));
+		
+	}
+	
+	public void testDoNotRestoreValueStackInRenderPhaseWhenProperPrg() throws Exception {
+		RenderRequest renderRequest = EasyMock.createNiceMock(RenderRequest.class);
+		ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class);
+		
+		
+		ValueStack eventPhaseStack = container.getInstance(ValueStackFactory.class).createValueStack();
+		eventPhaseStack.set("testKey", "testValue");
+		
+		ValueStack currentStack = container.getInstance(ValueStackFactory.class).createValueStack();
+		currentStack.set("anotherTestKey", "anotherTestValue");
+		
+		Map<String, Object> ctxMap = new HashMap<String, Object>();
+		Map<String, Object> session = new HashMap<String, Object>();
+		
+		session.put(STACK_FROM_EVENT_PHASE, eventPhaseStack);
+		
+		ctxMap.put(PHASE, RENDER_PHASE);
+		ctxMap.put(REQUEST, renderRequest);
+		
+		ActionContext ctx = new ActionContext(ctxMap);
+		ctx.setSession(session);
+		
+		EasyMock.expect(invocation.getInvocationContext()).andStubReturn(ctx);
+		EasyMock.expect(invocation.getStack()).andStubReturn(currentStack);
+		EasyMock.expect(invocation.getAction()).andStubReturn(new DefaultActionSupport());
+		EasyMock.expect(renderRequest.getParameter(EVENT_ACTION)).andStubReturn("true");
+		
+		EasyMock.replay(renderRequest);
+		EasyMock.replay(invocation);
+		
+		interceptor.intercept(invocation);
+		
+		ValueStack resultingStack = invocation.getStack();
+		
+		assertNull(resultingStack.findValue("testKey"));
+		assertEquals("anotherTestValue", resultingStack.findValue("anotherTestKey"));
+		
+		
+	}
+	
+	public void testRestoreValueStackInRenderPhaseWhenNotProperPrg() throws Exception {
+		RenderRequest renderRequest = EasyMock.createNiceMock(RenderRequest.class);
+		ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class);
+		
+		ValueStack eventPhaseStack = container.getInstance(ValueStackFactory.class).createValueStack();
+		eventPhaseStack.set("testKey", "testValue");
+		
+		ValueStack currentStack = container.getInstance(ValueStackFactory.class).createValueStack();
+		currentStack.set("anotherTestKey", "anotherTestValue");
+		
+		EasyMock.expect(invocation.getStack()).andStubReturn(currentStack);
+		
+		Map<String, Object> ctxMap = new HashMap<String, Object>();
+		Map<String, Object> session = new HashMap<String, Object>();
+		
+		session.put(STACK_FROM_EVENT_PHASE, eventPhaseStack);
+		
+		ctxMap.put(PHASE, RENDER_PHASE);
+		ctxMap.put(REQUEST, renderRequest);
+		
+		ActionContext ctx = new ActionContext(ctxMap);
+		ctx.setSession(session);
+		
+		EasyMock.expect(invocation.getInvocationContext()).andStubReturn(ctx);
+		EasyMock.expect(invocation.getStack()).andStubReturn(currentStack);
+		EasyMock.expect(invocation.getAction()).andStubReturn(new DirectRenderFromEventAction());
+		EasyMock.expect(renderRequest.getParameter(EVENT_ACTION)).andStubReturn("true");
+		
+		EasyMock.replay(renderRequest);
+		EasyMock.replay(invocation);
+		
+		interceptor.intercept(invocation);
+		
+		ValueStack resultingStack = invocation.getStack();
+		assertEquals("testValue", resultingStack.findValue("testKey"));
+		assertEquals("anotherTestValue", resultingStack.findValue("anotherTestKey"));
+		
+		
+	}
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/result/PortletResultTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/result/PortletResultTest.java
new file mode 100644
index 0000000..25fff49
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/result/PortletResultTest.java
@@ -0,0 +1,246 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.result;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import junit.textui.TestRunner;
+
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+import org.jmock.core.Constraint;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.mock.MockActionProxy;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+
+/**
+ * PortletResultTest. Insert description.
+ *
+ */
+public class PortletResultTest extends MockObjectTestCase implements PortletActionConstants {
+
+    Mock mockInvocation = null;
+    Mock mockConfig = null;
+    Mock mockCtx = null;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mockInvocation = mock(ActionInvocation.class);
+        mockCtx = mock(PortletContext.class);
+
+        Map paramMap = new HashMap();
+        Map sessionMap = new HashMap();
+
+        Map context = new HashMap();
+        context.put(ActionContext.SESSION, sessionMap);
+        context.put(ActionContext.PARAMETERS, paramMap);
+        context.put(StrutsStatics.STRUTS_PORTLET_CONTEXT, mockCtx.proxy());
+
+        ActionContext.setContext(new ActionContext(context));
+
+        mockInvocation.stubs().method("getInvocationContext").will(returnValue(ActionContext.getContext()));
+
+    }
+
+    public void testDoExecute_render() {
+        Mock mockRequest = mock(RenderRequest.class);
+        Mock mockResponse = mock(RenderResponse.class);
+        Mock mockRd = mock(PortletRequestDispatcher.class);
+
+        RenderRequest req = (RenderRequest)mockRequest.proxy();
+        RenderResponse res = (RenderResponse)mockResponse.proxy();
+        PortletRequestDispatcher rd = (PortletRequestDispatcher)mockRd.proxy();
+        PortletContext ctx = (PortletContext)mockCtx.proxy();
+        ActionInvocation inv = (ActionInvocation)mockInvocation.proxy();
+
+        Constraint[] params = new Constraint[]{same(req), same(res)};
+        mockRd.expects(once()).method("include").with(params);
+        mockCtx.expects(once()).method("getRequestDispatcher").with(eq("/WEB-INF/pages/testPage.jsp")).will(returnValue(rd));
+        mockResponse.expects(once()).method("setContentType").with(eq("text/html"));
+
+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));
+
+        ActionContext ctxMap = ActionContext.getContext();
+        ctxMap.put(PortletActionConstants.RESPONSE, res);
+        ctxMap.put(PortletActionConstants.REQUEST, req);
+        ctxMap.put(StrutsStatics.SERVLET_CONTEXT, ctx);
+        ctxMap.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+
+        PortletResult result = new PortletResult();
+        try {
+            result.doExecute("/WEB-INF/pages/testPage.jsp", inv);
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+            fail("Error occured!");
+        }
+
+    }
+
+    public void testDoExecute_event_locationIsAction() {
+
+        Mock mockRequest = mock(ActionRequest.class);
+        Mock mockResponse = mock(ActionResponse.class);
+
+        Constraint[] params = new Constraint[]{eq(PortletActionConstants.ACTION_PARAM), eq("testView")};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        params = new Constraint[]{eq(PortletActionConstants.MODE_PARAM), eq(PortletMode.VIEW.toString())};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));
+        ActionContext ctx = ActionContext.getContext();
+
+        ctx.put(PortletActionConstants.REQUEST, mockRequest.proxy());
+        ctx.put(PortletActionConstants.RESPONSE, mockResponse.proxy());
+        ctx.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+
+        PortletResult result = new PortletResult();
+        try {
+            result.doExecute("testView.action", (ActionInvocation)mockInvocation.proxy());
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+            fail("Error occured!");
+        }
+
+    }
+
+    public void testDoExecute_event_locationIsJsp() {
+        Mock mockRequest = mock(ActionRequest.class);
+        Mock mockResponse = mock(ActionResponse.class);
+        Mock mockProxy = mock(ActionProxy.class);
+
+        Constraint[] params = new Constraint[]{eq(PortletActionConstants.ACTION_PARAM), eq("renderDirect")};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        params = new Constraint[]{eq(PortletActionConstants.MODE_PARAM), eq(PortletMode.VIEW.toString())};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));
+        mockProxy.stubs().method("getNamespace").will(returnValue(""));
+
+        mockInvocation.stubs().method("getProxy").will(returnValue(mockProxy.proxy()));
+
+        ActionContext ctx = ActionContext.getContext();
+
+        Map session = new HashMap();
+        
+        ctx.put(PortletActionConstants.REQUEST, mockRequest.proxy());
+        ctx.put(PortletActionConstants.RESPONSE, mockResponse.proxy());
+        ctx.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+        ctx.put(ActionContext.SESSION, session);
+
+        PortletResult result = new PortletResult();
+        try {
+            result.doExecute("/WEB-INF/pages/testJsp.jsp", (ActionInvocation)mockInvocation.proxy());
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+            fail("Error occured!");
+        }
+        assertEquals("/WEB-INF/pages/testJsp.jsp", session.get(RENDER_DIRECT_LOCATION));
+    }
+
+    public void testDoExecute_event_locationHasQueryParams() {
+        Mock mockRequest = mock(ActionRequest.class);
+        Mock mockResponse = mock(ActionResponse.class);
+
+        Constraint[] params = new Constraint[]{eq(PortletActionConstants.ACTION_PARAM), eq("testView")};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        params = new Constraint[]{eq("testParam1"), eq("testValue1")};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        params = new Constraint[]{eq("testParam2"), eq("testValue2")};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        params = new Constraint[]{eq(PortletActionConstants.MODE_PARAM), eq(PortletMode.VIEW.toString())};
+        mockResponse.expects(once()).method("setRenderParameter").with(params);
+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));
+
+        ActionContext ctx = ActionContext.getContext();
+
+        ctx.put(PortletActionConstants.REQUEST, mockRequest.proxy());
+        ctx.put(PortletActionConstants.RESPONSE, mockResponse.proxy());
+        ctx.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);
+
+        PortletResult result = new PortletResult();
+        try {
+            result.doExecute("testView.action?testParam1=testValue1&testParam2=testValue2", (ActionInvocation)mockInvocation.proxy());
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+            fail("Error occured!");
+        }
+    }
+
+    public void testTitleAndContentType() throws Exception {
+        Mock mockRequest = mock(RenderRequest.class);
+        Mock mockResponse = mock(RenderResponse.class);
+        Mock mockRd = mock(PortletRequestDispatcher.class);
+
+        RenderRequest req = (RenderRequest)mockRequest.proxy();
+        RenderResponse res = (RenderResponse)mockResponse.proxy();
+        PortletRequestDispatcher rd = (PortletRequestDispatcher)mockRd.proxy();
+        PortletContext ctx = (PortletContext)mockCtx.proxy();
+
+        Constraint[] params = new Constraint[]{same(req), same(res)};
+        mockRd.expects(once()).method("include").with(params);
+        mockCtx.expects(once()).method("getRequestDispatcher").with(eq("/WEB-INF/pages/testPage.jsp")).will(returnValue(rd));
+
+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));
+
+        ActionContext ctxMap = ActionContext.getContext();
+        ctxMap.put(PortletActionConstants.RESPONSE, res);
+        ctxMap.put(PortletActionConstants.REQUEST, req);
+        ctxMap.put(StrutsStatics.SERVLET_CONTEXT, ctx);
+        ctxMap.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+
+        mockResponse.expects(atLeastOnce()).method("setTitle").with(eq("testTitle"));
+        mockResponse.expects(atLeastOnce()).method("setContentType").with(eq("testContentType"));
+
+        PortletResult result = new PortletResult();
+        result.setTitle("testTitle");
+        result.setContentType("testContentType");
+        result.doExecute("/WEB-INF/pages/testPage.jsp", (ActionInvocation)mockInvocation.proxy());
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+        ActionContext.setContext(null);
+    }
+
+    public static void main(String[] args) {
+        TestRunner.run(PortletResultTest.class);
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/servlet/PortletServletRequestTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/servlet/PortletServletRequestTest.java
new file mode 100644
index 0000000..9b26b86
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/servlet/PortletServletRequestTest.java
@@ -0,0 +1,49 @@
+package org.apache.struts2.portlet.servlet;

+

+import org.apache.struts2.ServletActionContext;

+import org.apache.struts2.StrutsTestCase;

+import org.apache.struts2.dispatcher.mapper.ActionMapping;

+import org.apache.struts2.portlet.PortletActionConstants;

+import org.apache.struts2.portlet.context.PortletActionContext;

+import org.springframework.mock.web.portlet.MockPortletContext;

+import org.springframework.mock.web.portlet.MockPortletRequest;

+

+import com.opensymphony.xwork2.ActionContext;

+

+public class PortletServletRequestTest extends StrutsTestCase {

+	

+	private MockPortletRequest portletRequest;

+	private MockPortletContext portletContext;

+	private PortletServletRequest request;

+	

+	protected void setUp() throws Exception {

+		super.setUp();

+		portletRequest = new MockPortletRequest();

+		portletContext = new MockPortletContext();

+		request = new PortletServletRequest(portletRequest, portletContext);

+	}

+	

+	public void testGetServletPathShouldHandleDefaultActionExtension() throws Exception {

+		portletRequest.setParameter(PortletActionConstants.ACTION_PARAM, "actionName");

+		request.setExtension("action");

+		assertEquals("actionName.action", request.getServletPath());

+	}

+	

+	public void testGetServletPathShouldHandleCustomActionExtension() throws Exception {

+		portletRequest.setParameter(PortletActionConstants.ACTION_PARAM, "actionName");

+		request.setExtension("custom");

+		assertEquals("actionName.custom", request.getServletPath());

+	}

+	

+	public void testGetServletPathShouldHandleNoExtension() throws Exception {

+		portletRequest.setParameter(PortletActionConstants.ACTION_PARAM, "actionName");

+		request.setExtension("");

+		assertEquals("actionName", request.getServletPath());

+	}

+	

+	public void testGetServletPathShouldHandleMultipleExtensionsByUsingTheFirst() throws Exception {

+		portletRequest.setParameter(PortletActionConstants.ACTION_PARAM, "actionName");

+		request.setExtension("action,,");

+		assertEquals("actionName.action", request.getServletPath());

+	}

+}

diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/util/PortletUrlHelperTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/util/PortletUrlHelperTest.java
new file mode 100644
index 0000000..0a5266a
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/portlet/util/PortletUrlHelperTest.java
@@ -0,0 +1,160 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.portlet.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.PortletMode;
+import javax.portlet.PortletURL;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.WindowState;
+
+import junit.framework.TestCase;
+
+import org.apache.struts2.portlet.context.PortletActionContext;
+import org.easymock.MockControl;
+
+import com.opensymphony.xwork2.ActionContext;
+
+/**
+ */
+public class PortletUrlHelperTest extends TestCase {
+
+    RenderResponse renderResponse;
+
+    RenderRequest renderRequest;
+
+    PortletURL url;
+
+    MockControl renderResponseControl;
+
+    MockControl renderRequestControl;
+
+    MockControl portletUrlControl;
+
+    public void setUp() throws Exception {
+        super.setUp();
+
+        renderRequestControl = MockControl.createControl(RenderRequest.class);
+        renderResponseControl = MockControl.createControl(RenderResponse.class);
+        portletUrlControl = MockControl.createControl(PortletURL.class);
+
+        renderRequest = (RenderRequest) renderRequestControl.getMock();
+        renderResponse = (RenderResponse) renderResponseControl.getMock();
+        url = (PortletURL) portletUrlControl.getMock();
+
+        renderRequestControl.expectAndDefaultReturn(renderRequest
+                .getPortletMode(), PortletMode.VIEW);
+        renderRequestControl.expectAndDefaultReturn(renderRequest
+                .getWindowState(), WindowState.NORMAL);
+
+        Map modeNamespaceMap = new HashMap();
+        modeNamespaceMap.put("view", "/view");
+        modeNamespaceMap.put("edit", "/edit");
+        modeNamespaceMap.put("help", "/help");
+
+        Map context = new HashMap();
+        context.put(PortletActionContext.REQUEST, renderRequest);
+        context.put(PortletActionContext.RESPONSE, renderResponse);
+        context.put(PortletActionContext.PHASE,
+                PortletActionContext.RENDER_PHASE);
+        context.put(PortletActionContext.MODE_NAMESPACE_MAP, modeNamespaceMap);
+
+        ActionContext.setContext(new ActionContext(context));
+
+    }
+
+    public void testCreateRenderUrlWithNoModeOrState() throws Exception {
+        renderResponseControl.expectAndReturn(renderResponse.createRenderURL(),
+                url);
+
+        url.setPortletMode(PortletMode.VIEW);
+        url.setWindowState(WindowState.NORMAL);
+        url.setParameters(null);
+        portletUrlControl.setMatcher(MockControl.ALWAYS_MATCHER);
+        renderRequestControl.replay();
+        renderResponseControl.replay();
+        portletUrlControl.replay();
+        PortletUrlHelper.buildUrl("testAction", null, null,
+                new HashMap(), null, null, null);
+        portletUrlControl.verify();
+        renderRequestControl.verify();
+        renderResponseControl.verify();
+    }
+
+    public void testCreateRenderUrlWithDifferentPortletMode() throws Exception {
+        renderResponseControl.expectAndReturn(renderResponse.createRenderURL(),
+                url);
+
+        url.setPortletMode(PortletMode.EDIT);
+        url.setWindowState(WindowState.NORMAL);
+        url.setParameters(null);
+        portletUrlControl.setMatcher(MockControl.ALWAYS_MATCHER);
+        renderRequestControl.replay();
+        renderResponseControl.replay();
+        portletUrlControl.replay();
+        PortletUrlHelper.buildUrl("testAction", null, null,
+                new HashMap(), null, "edit", null);
+        portletUrlControl.verify();
+        renderRequestControl.verify();
+        renderResponseControl.verify();
+    }
+
+    public void testCreateRenderUrlWithDifferentWindowState() throws Exception {
+        renderResponseControl.expectAndReturn(renderResponse.createRenderURL(),
+                url);
+
+        url.setPortletMode(PortletMode.VIEW);
+        url.setWindowState(WindowState.MAXIMIZED);
+        url.setParameters(null);
+        portletUrlControl.setMatcher(MockControl.ALWAYS_MATCHER);
+        renderRequestControl.replay();
+        renderResponseControl.replay();
+        portletUrlControl.replay();
+        PortletUrlHelper.buildUrl("testAction", null, null,
+                new HashMap(), null, null, "maximized");
+        portletUrlControl.verify();
+        renderRequestControl.verify();
+        renderResponseControl.verify();
+    }
+
+    public void testCreateActionUrl() throws Exception {
+        renderResponseControl.expectAndReturn(renderResponse.createActionURL(),
+                url);
+
+        url.setPortletMode(PortletMode.VIEW);
+        url.setWindowState(WindowState.NORMAL);
+        url.setParameters(null);
+        portletUrlControl.setMatcher(MockControl.ALWAYS_MATCHER);
+        renderRequestControl.replay();
+        renderResponseControl.replay();
+        portletUrlControl.replay();
+        PortletUrlHelper.buildUrl("testAction", null, null,
+                new HashMap(), "action", null, null);
+        portletUrlControl.verify();
+        renderRequestControl.verify();
+        renderResponseControl.verify();
+    }
+
+}
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/freemarker/PortletFreemarkerResultTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/freemarker/PortletFreemarkerResultTest.java
new file mode 100644
index 0000000..8ae72a6
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/freemarker/PortletFreemarkerResultTest.java
@@ -0,0 +1,105 @@
+/*

+ * $Id: $

+ *

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

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

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

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

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

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

+ *

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

+ *

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

+ * software distributed under the License is distributed on an

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

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

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.struts2.views.freemarker;

+

+import java.util.HashMap;

+import java.util.Map;

+

+import javax.portlet.ActionRequest;

+import javax.portlet.ActionResponse;

+import javax.portlet.PortletContext;

+import javax.portlet.PortletMode;

+

+import org.apache.struts2.StrutsStatics;

+import org.apache.struts2.portlet.PortletActionConstants;

+import org.jmock.Mock;

+import org.jmock.cglib.MockObjectTestCase;

+import org.jmock.core.Constraint;

+

+import com.opensymphony.xwork2.ActionContext;

+import com.opensymphony.xwork2.ActionInvocation;

+import com.opensymphony.xwork2.ActionProxy;

+

+public class PortletFreemarkerResultTest extends MockObjectTestCase implements PortletActionConstants {

+

+    Mock mockInvocation = null;

+    Mock mockConfig = null;

+    Mock mockCtx = null;

+

+    public void setUp() throws Exception {

+        super.setUp();

+        mockInvocation = mock(ActionInvocation.class);

+        mockCtx = mock(PortletContext.class);

+

+        Map paramMap = new HashMap();

+        Map sessionMap = new HashMap();

+

+        Map context = new HashMap();

+        context.put(ActionContext.SESSION, sessionMap);

+        context.put(ActionContext.PARAMETERS, paramMap);

+        context.put(StrutsStatics.STRUTS_PORTLET_CONTEXT, mockCtx.proxy());

+

+        ActionContext.setContext(new ActionContext(context));

+

+        mockInvocation.stubs().method("getInvocationContext").will(returnValue(ActionContext.getContext()));

+

+    }

+

+    public void testDoExecute_event_locationIsJsp() {

+        Mock mockRequest = mock(ActionRequest.class);

+        Mock mockResponse = mock(ActionResponse.class);

+        Mock mockProxy = mock(ActionProxy.class);

+

+        Constraint[] params = new Constraint[]{eq(PortletActionConstants.ACTION_PARAM), eq("freemarkerDirect")};

+        mockResponse.expects(once()).method("setRenderParameter").with(params);

+        params = new Constraint[]{eq(PortletActionConstants.MODE_PARAM), eq(PortletMode.VIEW.toString())};

+        mockResponse.expects(once()).method("setRenderParameter").with(params);

+        mockRequest.stubs().method("getPortletMode").will(returnValue(PortletMode.VIEW));

+        mockProxy.stubs().method("getNamespace").will(returnValue(""));

+

+        mockInvocation.stubs().method("getProxy").will(returnValue(mockProxy.proxy()));

+

+        ActionContext ctx = ActionContext.getContext();

+

+        Map session = new HashMap();

+        

+        ctx.put(PortletActionConstants.REQUEST, mockRequest.proxy());

+        ctx.put(PortletActionConstants.RESPONSE, mockResponse.proxy());

+        ctx.put(PortletActionConstants.PHASE, PortletActionConstants.EVENT_PHASE);

+        ctx.put(ActionContext.SESSION, session);

+

+        PortletFreemarkerResult result = new PortletFreemarkerResult();

+        try {

+            result.doExecute("/WEB-INF/pages/testJsp.ftl", (ActionInvocation)mockInvocation.proxy());

+        }

+        catch(Exception e) {

+            e.printStackTrace();

+            fail("Error occured!");

+        }

+        assertEquals("/WEB-INF/pages/testJsp.ftl", session.get(RENDER_DIRECT_LOCATION));

+    }

+

+

+    public void tearDown() throws Exception {

+        super.tearDown();

+        ActionContext.setContext(null);

+    }

+}
\ No newline at end of file
diff --git a/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/jsp/PortletUrlTagTest.java b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/jsp/PortletUrlTagTest.java
new file mode 100644
index 0000000..6487bcc
--- /dev/null
+++ b/plugins/struts2-portlet-plugin/src/test/java/org/apache/struts2/views/jsp/PortletUrlTagTest.java
@@ -0,0 +1,399 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.struts2.views.jsp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletURL;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.StrutsTestCase;
+import org.apache.struts2.portlet.PortletActionConstants;
+import org.apache.struts2.portlet.servlet.PortletServletRequest;
+import org.apache.struts2.portlet.util.PortletUrlHelper;
+import org.springframework.mock.web.portlet.MockPortalContext;
+import org.springframework.mock.web.portlet.MockPortletContext;
+import org.springframework.mock.web.portlet.MockPortletURL;
+import org.springframework.mock.web.portlet.MockRenderRequest;
+import org.springframework.mock.web.portlet.MockRenderResponse;
+
+import com.mockobjects.servlet.MockJspWriter;
+import com.mockobjects.servlet.MockPageContext;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import com.opensymphony.xwork2.mock.MockActionProxy;
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ */
+@SuppressWarnings("unchecked")
+public class PortletUrlTagTest extends StrutsTestCase {
+
+	private URLTag tag = new URLTag();
+
+	private ValueStack stack = null;
+
+	private ActionContext context = null;
+
+	private MockRenderRequest renderRequest;
+
+	private MockPortletUrl renderUrl;
+
+	private MockPortletUrl actionUrl;
+
+	private MockRenderResponse renderResponse;
+
+	private MockPageContext pageContext;
+
+	private MockActionInvocation actionInvocation;
+
+	private MockActionProxy actionProxy;
+
+	private MockJspWriter jspWriter;
+	
+	private MockPortletContext portletContext;
+
+	public void setUp() throws Exception {
+		super.setUp();
+
+		context = ActionContext.getContext();
+		stack = context.getValueStack();
+
+		portletContext = new MockPortletContext();
+		renderRequest = new MockRenderRequest();
+		renderRequest.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
+		renderUrl = new MockPortletUrl("render");
+		actionUrl = new MockPortletUrl("action");
+		renderResponse = new MockRenderResponse() {
+			@Override
+			public PortletURL createRenderURL() {
+				return renderUrl;
+			}
+
+			@Override
+			public PortletURL createActionURL() {
+				return actionUrl;
+			}
+		};
+
+		Map modeMap = new HashMap();
+		modeMap.put(PortletMode.VIEW, "/view");
+		modeMap.put(PortletMode.HELP, "/help");
+		modeMap.put(PortletMode.EDIT, "/edit");
+
+		context.put(PortletActionConstants.REQUEST, renderRequest);
+		context.put(PortletActionConstants.RESPONSE, renderResponse);
+		context.put(PortletActionConstants.PHASE, PortletActionConstants.RENDER_PHASE);
+		context.put(PortletActionConstants.MODE_NAMESPACE_MAP, modeMap);
+		context.put(StrutsStatics.STRUTS_PORTLET_CONTEXT, portletContext);
+
+		actionInvocation = new MockActionInvocation();
+		actionProxy = new MockActionProxy();
+
+		actionInvocation.setAction(new Object());
+		actionInvocation.setProxy(actionProxy);
+		actionInvocation.setStack(stack);
+
+		context.setActionInvocation(actionInvocation);
+
+		pageContext = new MockPageContext();
+		pageContext.setRequest(new PortletServletRequest(renderRequest, null));
+		jspWriter = new MockJspWriter();
+		pageContext.setJspWriter(jspWriter);
+
+		tag.setPageContext(pageContext);
+
+	}
+
+	public void testEnsureParamsAreStringArrays() {
+		Map params = new HashMap();
+		params.put("param1", "Test1");
+		params.put("param2", new String[] { "Test2" });
+
+		Map result = PortletUrlHelper.ensureParamsAreStringArrays(params);
+		assertEquals(2, result.size());
+		assertTrue(result.get("param1") instanceof String[]);
+	}
+
+	public void testSetWindowState() throws Exception {
+
+		tag.setAction("testAction");
+		tag.setWindowState("maximized");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.MAXIMIZED, renderUrl.getWindowState());
+
+	}
+
+	public void testSetPortletMode() throws Exception {
+
+		tag.setAction("testAction");
+		tag.setPortletMode("help");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/help/testAction", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals(PortletMode.HELP.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.HELP, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	public void testUrlWithQueryParams() throws Exception {
+
+		tag.setAction("testAction?testParam1=testValue1");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals("testValue1", renderUrl.getParameter("testParam1"));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	public void testActionUrl() throws Exception {
+
+		tag.setAction("testAction");
+		tag.setPortletUrlType("action");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction", actionUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals(PortletMode.VIEW, actionUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, actionUrl.getWindowState());
+	}
+
+	public void testResourceUrl() throws Exception {
+		renderRequest.setContextPath("/myPortlet");
+		jspWriter.setExpectedData("/myPortlet/image.gif");
+		tag.setValue("image.gif");
+		tag.doStartTag();
+		tag.doEndTag();
+		jspWriter.verify();
+	}
+
+	public void testResourceUrlWithNestedParam() throws Exception {
+		renderRequest.setContextPath("/myPortlet");
+		jspWriter.setExpectedData("/myPortlet/image.gif?testParam1=testValue1");
+
+		ParamTag paramTag = new ParamTag();
+		paramTag.setPageContext(pageContext);
+		paramTag.setParent(tag);
+		paramTag.setName("testParam1");
+		paramTag.setValue("'testValue1'");
+		tag.setValue("image.gif");
+		tag.doStartTag();
+		paramTag.doStartTag();
+		paramTag.doEndTag();
+		tag.doEndTag();
+		jspWriter.verify();
+	}
+
+	public void testResourceUrlWithTwoNestedParam() throws Exception {
+		renderRequest.setContextPath("/myPortlet");
+		jspWriter.setExpectedData("/myPortlet/image.gif?testParam1=testValue1&testParam2=testValue2");
+
+		ParamTag paramTag = new ParamTag();
+		paramTag.setPageContext(pageContext);
+		paramTag.setParent(tag);
+		paramTag.setName("testParam1");
+		paramTag.setValue("'testValue1'");
+		ParamTag paramTag2 = new ParamTag();
+		paramTag2.setPageContext(pageContext);
+		paramTag2.setParent(tag);
+		paramTag2.setName("testParam2");
+		paramTag2.setValue("'testValue2'");
+		tag.setValue("image.gif");
+		tag.doStartTag();
+		paramTag.doStartTag();
+		paramTag.doEndTag();
+		paramTag2.doStartTag();
+		paramTag2.doEndTag();
+		tag.doEndTag();
+		jspWriter.verify();
+	}
+	
+	public void testResourceUrlWithNestedParamThatIsNotString() throws Exception {
+		renderRequest.setContextPath("/myPortlet");
+		jspWriter.setExpectedData("/myPortlet/image.gif?id=5");
+		
+		ParamTag paramTag = new ParamTag();
+		paramTag.setPageContext(pageContext);
+		paramTag.setParent(tag);
+		paramTag.setName("id");
+		paramTag.setValue("5");
+		
+		tag.setValue("image.gif");
+		tag.doStartTag();
+		paramTag.doStartTag();
+		paramTag.doEndTag();
+		tag.doEndTag();
+		jspWriter.verify();
+	}
+	
+	public void testResourceUrlWithNestedOgnlExpressionParamThatIsNotString() throws Exception {
+		renderRequest.setContextPath("/myPortlet");
+		jspWriter.setExpectedData("/myPortlet/image.gif?id=5");
+		
+		Object o = new Object() {
+			public Integer getId() {
+				return 5;
+			}
+		};
+		tag.getStack().push(o);
+		
+		ParamTag paramTag = new ParamTag();
+		paramTag.setPageContext(pageContext);
+		paramTag.setParent(tag);
+		paramTag.setName("id");
+		paramTag.setValue("id");
+		
+		tag.setValue("image.gif");
+		tag.doStartTag();
+		paramTag.doStartTag();
+		paramTag.doEndTag();
+		tag.doEndTag();
+		jspWriter.verify();
+	}
+
+	public void testUrlWithMethod() throws Exception {
+		tag.setAction("testAction");
+		tag.setMethod("input");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction!input", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	public void testUrlWithNoActionOrMethod() throws Exception {
+		actionProxy.setActionName("currentExecutingAction");
+		actionProxy.setNamespace("/currentNamespace");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/currentNamespace/currentExecutingAction", renderUrl
+				.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	public void testUrlShouldNotIncludeParamsFromHttpQueryString() throws Exception {
+
+		PortletServletRequestWithQueryString req = new PortletServletRequestWithQueryString(renderRequest, null);
+		req.setQueryString("thisParamShouldNotBeIncluded=thisValueShouldNotBeIncluded");
+		pageContext.setRequest(req);
+		tag.setAction("testAction?testParam1=testValue1");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals("testValue1", renderUrl.getParameter("testParam1"));
+		assertNull(renderUrl.getParameter("thisParamShouldNotBeIncluded"));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	public void testUrlShouldIgnoreIncludeParams() throws Exception {
+		PortletServletRequestWithQueryString req = new PortletServletRequestWithQueryString(renderRequest, null);
+		req.setQueryString("thisParamShouldNotBeIncluded=thisValueShouldNotBeIncluded");
+		pageContext.setRequest(req);
+		tag.setAction("testAction?testParam1=testValue1");
+		tag.setIncludeParams("GET");
+		tag.doStartTag();
+		tag.doEndTag();
+
+		assertEquals("/view/testAction", renderUrl.getParameter(PortletActionConstants.ACTION_PARAM));
+		assertEquals("testValue1", renderUrl.getParameter("testParam1"));
+		assertNull(renderUrl.getParameter("thisParamShouldNotBeIncluded"));
+		assertEquals(PortletMode.VIEW.toString(), renderUrl.getParameter(PortletActionConstants.MODE_PARAM));
+		assertEquals(PortletMode.VIEW, renderUrl.getPortletMode());
+		assertEquals(WindowState.NORMAL, renderUrl.getWindowState());
+	}
+
+	private static class PortletServletRequestWithQueryString extends PortletServletRequest {
+
+		private String queryString;
+
+		public PortletServletRequestWithQueryString(PortletRequest portletRequest, PortletContext portletContext) {
+			super(portletRequest, portletContext);
+		}
+
+		public void setQueryString(String queryString) {
+			this.queryString = queryString;
+		}
+
+		@Override
+		public String getQueryString() {
+			return queryString;
+		}
+
+	}
+
+	private static class MockPortletUrl extends MockPortletURL {
+
+		private PortletMode portletMode;
+
+		private WindowState windowState;
+
+		public MockPortletUrl(String urlType) {
+			super(new MockPortalContext(), urlType);
+		}
+
+		@Override
+		public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+			super.setPortletMode(portletMode);
+			this.portletMode = portletMode;
+		}
+
+		public PortletMode getPortletMode() {
+			return portletMode;
+		}
+
+		@Override
+		public void setWindowState(WindowState windowState) throws WindowStateException {
+			super.setWindowState(windowState);
+			this.windowState = windowState;
+		}
+
+		public WindowState getWindowState() {
+			return windowState;
+		}
+
+	}
+}