Initial import of portlet module

This commit is contained in:
Arjen Poutsma 2008-10-27 12:01:57 +00:00
parent 995a323a6f
commit 4ea298a7b0
80 changed files with 14453 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="org.springframework.web.portlet">
<property file="${basedir}/../build.properties"/>
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
<import file="${basedir}/../spring-build/standard/default.xml"/>
</project>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
<ivy-module
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
version="1.3">
<info organisation="org.springframework" module="${ant.project.name}">
<license name="Apache 2.0" url="http://www.apache.org/licenses/LICENSE-2.0"/>
</info>
<configurations>
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
<conf name="commons-fileupload" extends="runtime" description="JARs needed to run with Commons Fileupload"/>
</configurations>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<dependency org="javax.portlet" name="com.springsource.javax.portlet" rev="1.0.0" conf="provided->compile"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.4.0" conf="provided->compile"/>
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.fileupload" rev="1.2.0" conf="optional, commons-fileupload->compile"/>
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.beans" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.web" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.web.servlet" rev="latest.integration" conf="compile->compile"/>
</dependencies>
</ivy-module>

View File

@ -0,0 +1,86 @@
<?xml version="1.0"?>
<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">
<parent>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.parent</artifactId>
<version>3.0-M1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.springframework.web</artifactId>
<packaging>jar</packaging>
<name>Spring Framework: Web</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>com.springsource.javax.el</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet.jsp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.rpc</groupId>
<artifactId>com.springsource.javax.xml.rpc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.myfaces</groupId>
<artifactId>com.springsource.org.apache.myfaces.javax.faces</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>com.springsource.com.caucho</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.log4j</groupId>
<artifactId>com.springsource.org.apache.log4j</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>com.springsource.org.apache.commons.httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>com.springsource.org.apache.axis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>com.springsource.org.apache.taglibs.standard</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
# Default implementation classes for DispatcherPortlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherPortlet context.
# Not meant to be customized by application developers.
org.springframework.web.portlet.HandlerMapping=org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.portlet.HandlerAdapter=org.springframework.web.portlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

View File

@ -0,0 +1,599 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SourceFilteringListener;
import org.springframework.web.portlet.context.ConfigurablePortletApplicationContext;
import org.springframework.web.portlet.context.PortletApplicationContextUtils;
import org.springframework.web.portlet.context.PortletRequestHandledEvent;
import org.springframework.web.portlet.context.XmlPortletApplicationContext;
/**
* Base portlet for Spring's portlet framework. Provides integration with
* a Spring application context, in a JavaBean-based overall solution.
*
* <p>This class offers the following functionality:
* <ul>
* <li>Manages a Portlet {@link org.springframework.context.ApplicationContext}
* instance per portlet. The portlet's configuration is determined by beans
* in the portlet's namespace.
* <li>Publishes events on request processing, whether or not a request is
* successfully handled.
* </ul>
*
* <p>Subclasses must implement {@link #doActionService} and {@link #doRenderService}
* to handle action and render requests. Because this extends {@link GenericPortletBean}
* rather than Portlet directly, bean properties are mapped onto it. Subclasses can
* override {@link #initFrameworkPortlet()} for custom initialization.
*
* <p>Regards a "contextClass" parameter at the portlet init-param level,
* falling back to the default context class
* ({@link org.springframework.web.portlet.context.XmlPortletApplicationContext})
* if not found. Note that, with the default FrameworkPortlet,
* a context class needs to implement the
* {@link org.springframework.web.portlet.context.ConfigurablePortletApplicationContext} SPI.
*
* <p>Passes a "contextConfigLocation" portlet init-param to the context instance,
* parsing it into potentially multiple file paths which can be separated by any
* number of commas and spaces, like "test-portlet.xml, myPortlet.xml".
* If not explicitly specified, the context implementation is supposed to build a
* default location from the namespace of the portlet.
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files, at least when using one of
* Spring's default ApplicationContext implementations. This can be leveraged
* to deliberately override certain bean definitions via an extra XML file.
*
* <p>The default namespace is "'portlet-name'-portlet", e.g. "test-portlet" for a
* portlet-name "test" (leading to a "/WEB-INF/test-portlet.xml" default location
* with XmlPortletApplicationContext). The namespace can also be set explicitly via
* the "namespace" portlet init-param.
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see #doActionService
* @see #doRenderService
* @see #setContextClass
* @see #setContextConfigLocation
* @see #setNamespace
*/
public abstract class FrameworkPortlet extends GenericPortletBean implements ApplicationListener {
/**
* Default context class for FrameworkPortlet.
* @see org.springframework.web.portlet.context.XmlPortletApplicationContext
*/
public static final Class DEFAULT_CONTEXT_CLASS = XmlPortletApplicationContext.class;
/**
* Suffix for Portlet ApplicationContext namespaces. If a portlet of this class is
* given the name "test" in a context, the namespace used by the portlet will
* resolve to "test-portlet".
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-portlet";
/**
* Prefix for the PortletContext attribute for the Portlet ApplicationContext.
* The completion is the portlet name.
*/
public static final String PORTLET_CONTEXT_PREFIX = FrameworkPortlet.class.getName() + ".CONTEXT.";
/**
* Default USER_INFO attribute names to search for the current username:
* "user.login.id", "user.name".
*/
public static final String[] DEFAULT_USERINFO_ATTRIBUTE_NAMES = {"user.login.id", "user.name"};
/** Portlet ApplicationContext implementation class to use */
private Class contextClass = DEFAULT_CONTEXT_CLASS;
/** Namespace for this portlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation;
/** Should we publish the context as a PortletContext attribute? */
private boolean publishContext = true;
/** Should we publish a PortletRequestHandledEvent at the end of each request? */
private boolean publishEvents = true;
/** USER_INFO attributes that may contain the username of the current user */
private String[] userinfoUsernameAttributes = DEFAULT_USERINFO_ATTRIBUTE_NAMES;
/** ApplicationContext for this portlet */
private ApplicationContext portletApplicationContext;
/** Flag used to detect whether onRefresh has already been called */
private boolean refreshEventReceived = false;
/**
* Set a custom context class. This class must be of type ApplicationContext;
* when using the default FrameworkPortlet implementation, the context class
* must also implement ConfigurablePortletApplicationContext.
* @see #createPortletApplicationContext
*/
public void setContextClass(Class contextClass) {
this.contextClass = contextClass;
}
/**
* Return the custom context class.
*/
public Class getContextClass() {
return this.contextClass;
}
/**
* Set a custom namespace for this portlet,
* to be used for building a default context config location.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the namespace for this portlet, falling back to default scheme if
* no custom namespace was set. (e.g. "test-portlet" for a portlet named "test")
*/
public String getNamespace() {
return (this.namespace != null) ? this.namespace : getPortletName() + DEFAULT_NAMESPACE_SUFFIX;
}
/**
* Set the context config location explicitly, instead of relying on the default
* location built from the namespace. This location string can consist of
* multiple locations separated by any number of commas and spaces.
*/
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* Return the explicit context config location, if any.
*/
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
/**
* Set whether to publish this portlet's context as a PortletContext attribute,
* available to all objects in the web container. Default is true.
* <p>This is especially handy during testing, although it is debatable whether
* it's good practice to let other application objects access the context this way.
*/
public void setPublishContext(boolean publishContext) {
this.publishContext = publishContext;
}
/**
* Return whether to publish this portlet's context as a PortletContext attribute.
*/
public boolean isPublishContext() {
return this.publishContext;
}
/**
* Set whether this portlet should publish a PortletRequestHandledEvent at the end
* of each request. Default is true; can be turned off for a slight performance
* improvement, provided that no ApplicationListeners rely on such events.
* @see org.springframework.web.portlet.context.PortletRequestHandledEvent
*/
public void setPublishEvents(boolean publishEvents) {
this.publishEvents = publishEvents;
}
/**
* Return whether this portlet should publish a PortletRequestHandledEvent at the end
* of each request.
*/
public boolean isPublishEvents() {
return this.publishEvents;
}
/**
* Set the list of attributes to search in the USER_INFO map when trying
* to find the username of the current user.
* @see #getUsernameForRequest
*/
public void setUserinfoUsernameAttributes(String[] userinfoUsernameAttributes) {
this.userinfoUsernameAttributes = userinfoUsernameAttributes;
}
/**
* Returns the list of attributes that will be searched in the USER_INFO map
* when trying to find the username of the current user
* @see #getUsernameForRequest
*/
public String[] getUserinfoUsernameAttributes() {
return this.userinfoUsernameAttributes;
}
/**
* Overridden method of GenericPortletBean, invoked after any bean properties
* have been set. Creates this portlet's ApplicationContext.
*/
protected final void initPortletBean() throws PortletException, BeansException {
getPortletContext().log("Initializing Spring FrameworkPortlet '" + getPortletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("FrameworkPortlet '" + getPortletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.portletApplicationContext = initPortletApplicationContext();
initFrameworkPortlet();
}
catch (PortletException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
catch (BeansException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("FrameworkPortlet '" + getPortletName() + "': initialization completed in " + elapsedTime + " ms");
}
}
/**
* Initialize and publish the Portlet ApplicationContext for this portlet.
* <p>Delegates to {@link #createPortletApplicationContext} for actual creation.
* Can be overridden in subclasses.
* @return the ApplicationContext for this portlet
* @throws BeansException if the context couldn't be initialized
*/
protected ApplicationContext initPortletApplicationContext() throws BeansException {
ApplicationContext parent = PortletApplicationContextUtils.getWebApplicationContext(getPortletContext());
ApplicationContext pac = createPortletApplicationContext(parent);
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(pac);
}
if (isPublishContext()) {
// publish the context as a portlet context attribute
String attName = getPortletContextAttributeName();
getPortletContext().setAttribute(attName, pac);
if (logger.isDebugEnabled()) {
logger.debug("Published ApplicationContext of portlet '" + getPortletName() +
"' as PortletContext attribute with name [" + attName + "]");
}
}
return pac;
}
/**
* Instantiate the Portlet ApplicationContext for this portlet, either a default
* XmlPortletApplicationContext or a custom context class if set.
* <p>This implementation expects custom contexts to implement
* ConfigurablePortletApplicationContext. Can be overridden in subclasses.
* @param parent the parent ApplicationContext to use, or null if none
* @return the Portlet ApplicationContext for this portlet
* @throws BeansException if the context couldn't be initialized
* @see #setContextClass
* @see org.springframework.web.portlet.context.XmlPortletApplicationContext
*/
protected ApplicationContext createPortletApplicationContext(ApplicationContext parent)
throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Portlet with name '" + getPortletName() +
"' will try to create custom ApplicationContext context of class '" +
getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurablePortletApplicationContext.class.isAssignableFrom(getContextClass())) {
throw new ApplicationContextException("Fatal initialization error in portlet with name '" + getPortletName() +
"': custom ApplicationContext class [" + getContextClass().getName() +
"] is not of type ConfigurablePortletApplicationContext");
}
ConfigurablePortletApplicationContext pac =
(ConfigurablePortletApplicationContext) BeanUtils.instantiateClass(getContextClass());
pac.setParent(parent);
pac.setPortletContext(getPortletContext());
pac.setPortletConfig(getPortletConfig());
pac.setNamespace(getNamespace());
pac.setConfigLocation(getContextConfigLocation());
pac.addApplicationListener(new SourceFilteringListener(pac, this));
postProcessPortletApplicationContext(pac);
pac.refresh();
return pac;
}
/**
* Post-process the given Portlet ApplicationContext before it is refreshed
* and activated as context for this portlet.
* <p>The default implementation is empty. <code>refresh()</code> will
* be called automatically after this method returns.
* @param pac the configured Portlet ApplicationContext (not refreshed yet)
* @see #createPortletApplicationContext
* @see ConfigurableApplicationContext#refresh()
*/
protected void postProcessPortletApplicationContext(ConfigurableApplicationContext pac) {
}
/**
* Return the PortletContext attribute name for this portlets's ApplicationContext.
* <p>The default implementation returns PORTLET_CONTEXT_PREFIX + portlet name.
* @see #PORTLET_CONTEXT_PREFIX
* @see #getPortletName
*/
public String getPortletContextAttributeName() {
return PORTLET_CONTEXT_PREFIX + getPortletName();
}
/**
* Return this portlet's ApplicationContext.
*/
public final ApplicationContext getPortletApplicationContext() {
return this.portletApplicationContext;
}
/**
* This method will be invoked after any bean properties have been set and
* the ApplicationContext has been loaded.
* <p>The default implementation is empty; subclasses may override this method
* to perform any initialization they require.
* @throws PortletException in case of an initialization exception
* @throws BeansException if thrown by ApplicationContext methods
*/
protected void initFrameworkPortlet() throws PortletException, BeansException {
}
/**
* Refresh this portlet's application context, as well as the
* dependent state of the portlet.
* @throws BeansException in case of errors
* @see #getPortletApplicationContext()
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
public void refresh() throws BeansException {
ApplicationContext pac = getPortletApplicationContext();
if (!(pac instanceof ConfigurableApplicationContext)) {
throw new IllegalStateException("Portlet ApplicationContext does not support refresh: " + pac);
}
((ConfigurableApplicationContext) pac).refresh();
}
/**
* ApplicationListener endpoint that receives events from this servlet's
* WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh} in case of a
* {@link org.springframework.context.event.ContextRefreshedEvent},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
this.refreshEventReceived = true;
onRefresh(((ContextRefreshedEvent) event).getApplicationContext());
}
}
/**
* Template method which can be overridden to add portlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current Portlet ApplicationContext
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) throws BeansException {
// For subclasses: do nothing by default.
}
/**
* Overridden for friendlier behavior in unit tests.
*/
protected String getTitle(RenderRequest renderRequest) {
try {
return super.getTitle(renderRequest);
}
catch (NullPointerException ex) {
return getPortletName();
}
}
/**
* Delegate render requests to processRequest/doRenderService.
*/
protected final void doDispatch(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
processRequest(request, response);
}
/**
* Delegate action requests to processRequest/doActionService.
*/
public final void processAction(ActionRequest request, ActionResponse response)
throws PortletException, IOException {
processRequest(request, response);
}
/**
* Process this request, publishing an event regardless of the outcome.
* The actual event handling is performed by the abstract
* <code>doActionService()</code> and <code>doRenderService()</code> template methods.
* @see #doActionService
* @see #doRenderService
*/
protected final void processRequest(PortletRequest request, PortletResponse response)
throws PortletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
try {
if (request instanceof ActionRequest) {
doActionService((ActionRequest) request, (ActionResponse) response);
}
else {
doRenderService((RenderRequest) request, (RenderResponse) response);
}
}
catch (PortletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new PortletException("Request processing failed", ex);
}
finally {
if (failureCause != null) {
logger.error("Could not complete request", failureCause);
}
else {
logger.debug("Successfully completed request");
}
if (isPublishEvents()) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.portletApplicationContext.publishEvent(
new PortletRequestHandledEvent(this,
getPortletConfig().getPortletName(), request.getPortletMode().toString(),
(request instanceof ActionRequest ? "action" : "render"),
request.getRequestedSessionId(), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
/**
* Determine the username for the given request.
* <p>The default implementation first tries the UserPrincipal.
* If that does not exist, then it checks the USER_INFO map.
* Can be overridden in subclasses.
* @param request current portlet request
* @return the username, or <code>null</code> if none found
* @see javax.portlet.PortletRequest#getUserPrincipal()
* @see javax.portlet.PortletRequest#getRemoteUser()
* @see javax.portlet.PortletRequest#USER_INFO
* @see #setUserinfoUsernameAttributes
*/
protected String getUsernameForRequest(PortletRequest request) {
// Try the principal.
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null) {
return userPrincipal.getName();
}
// Try the remote user name.
String userName = request.getRemoteUser();
if (userName != null) {
return userName;
}
// Try the Portlet USER_INFO map.
Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
if (userInfo != null) {
for (int i = 0, n = this.userinfoUsernameAttributes.length; i < n; i++) {
userName = (String) userInfo.get(this.userinfoUsernameAttributes[i]);
if (userName != null) {
return userName;
}
}
}
// Nothing worked...
return null;
}
/**
* Subclasses must implement this method to do the work of render request handling.
* <p>The contract is essentially the same as that for the <code>doDispatch</code>
* method of GenericPortlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current render request
* @param response current render response
* @throws Exception in case of any kind of processing failure
* @see javax.portlet.GenericPortlet#doDispatch
*/
protected abstract void doRenderService(RenderRequest request, RenderResponse response)
throws Exception;
/**
* Subclasses must implement this method to do the work of action request handling.
* <p>The contract is essentially the same as that for the <code>processAction</code>
* method of GenericPortlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current action request
* @param response current action response
* @throws Exception in case of any kind of processing failure
* @see javax.portlet.GenericPortlet#processAction
*/
protected abstract void doActionService(ActionRequest request, ActionResponse response)
throws Exception;
/**
* Close the ApplicationContext of this portlet.
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
public void destroy() {
getPortletContext().log("Destroying Spring FrameworkPortlet '" + getPortletName() + "'");
if (this.portletApplicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.portletApplicationContext).close();
}
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import org.springframework.web.portlet.context.PortletContextResourceLoader;
/**
* Simple extension of <code>javax.portlet.GenericPortlet</code> that treats
* its config parameters as bean properties.
*
* <p>A very handy superclass for any type of portlet. Type conversion is automatic.
* It is also possible for subclasses to specify required properties.
*
* <p>This portlet leaves request handling to subclasses, inheriting the default
* behaviour of GenericPortlet (<code>doDispatch</code>, <code>processAction</code>, etc).
*
* <p>This portlet superclass has no dependency on a Spring application context,
* in contrast to the FrameworkPortlet class which loads its own context.
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see #addRequiredProperty
* @see #initPortletBean
* @see #doDispatch
* @see #processAction
* @see FrameworkPortlet
*/
public abstract class GenericPortletBean extends GenericPortlet {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
/**
* Set of required properties (Strings) that must be supplied as
* config parameters to this portlet.
*/
private final Set requiredProperties = new HashSet();
/**
* Subclasses can invoke this method to specify that this property
* (which must match a JavaBean property they expose) is mandatory,
* and must be supplied as a config parameter. This method would
* normally be called from a subclass constructor.
* @param property name of the required property
*/
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}
/**
* Map config parameters onto bean properties of this portlet, and
* invoke subclass initialization.
* @throws PortletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
public final void init() throws PortletException {
if (logger.isInfoEnabled()) {
logger.info("Initializing portlet '" + getPortletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new PortletConfigPropertyValues(getPortletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new PortletContextResourceLoader(getPortletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on portlet '" + getPortletName() + "'", ex);
throw ex;
}
// let subclasses do whatever initialization they like
initPortletBean();
if (logger.isInfoEnabled()) {
logger.info("Portlet '" + getPortletName() + "' configured successfully");
}
}
/**
* Initialize the BeanWrapper for this GenericPortletBean,
* possibly with custom editors.
* @param bw the BeanWrapper to initialize
* @throws BeansException if thrown by BeanWrapper methods
* @see org.springframework.beans.BeanWrapper#registerCustomEditor
*/
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
/**
* Overridden method that simply returns <code>null</code> when no
* PortletConfig set yet.
* @see #getPortletConfig()
*/
public final String getPortletName() {
return (getPortletConfig() != null ? getPortletConfig().getPortletName() : null);
}
/**
* Overridden method that simply returns <code>null</code> when no
* PortletConfig set yet.
* @see #getPortletConfig()
*/
public final PortletContext getPortletContext() {
return (getPortletConfig() != null ? getPortletConfig().getPortletContext() : null);
}
/**
* Subclasses may override this to perform custom initialization.
* All bean properties of this portlet will have been set before this
* method is invoked. This default implementation does nothing.
* @throws PortletException if subclass initialization fails
*/
protected void initPortletBean() throws PortletException {
}
/**
* PropertyValues implementation created from PortletConfig init parameters.
*/
private static class PortletConfigPropertyValues extends MutablePropertyValues {
/**
* Create new PortletConfigPropertyValues.
* @param config PortletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws PortletException if any required properties are missing
*/
private PortletConfigPropertyValues(PortletConfig config, Set requiredProperties)
throws PortletException {
Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
new HashSet(requiredProperties) : null;
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = (String) en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// fail if we are still missing properties
if (missingProps != null && missingProps.size() > 0) {
throw new PortletException(
"Initialization from PortletConfig for portlet '" + config.getPortletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
/**
* Portlet MVC framework SPI interface, allowing parameterization of core MVC workflow.
*
* <p>Interface that must be implemented for each handler type to handle a request.
* This interface is used to allow the DispatcherPortlet to be indefinitely
* extensible. The DispatcherPortlet accesses all installed handlers through this
* interface, meaning that it does not contain code specific to any handler type.
*
* <p>Note that a handler can be of type Object. This is to enable handlers from
* other frameworks to be integrated with this framework without custom coding.
*
* <p>This interface is not intended for application developers. It is available
* to handlers who want to develop their own web workflow.
*
* <p>Note: Implementations can implement the Ordered interface to be able to
* specify a sorting order and thus a priority for getting applied by
* DispatcherPortlet. Non-Ordered instances get treated as lowest priority.
*
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.mvc.SimpleControllerHandlerAdapter
*/
public interface HandlerAdapter {
/**
* Given a handler instance, return whether or not this HandlerAdapter can
* support it. Typical HandlerAdapters will base the decision on the handler
* type. HandlerAdapters will usually only support one handler type each.
* <p>A typical implementation:
* <p><code>
* return (handler instanceof MyHandler);
* </code>
* @param handler handler object to check
* @return whether or not this object can use the given handler
*/
boolean supports(Object handler);
/**
* Use the given handler to handle this action request.
* The workflow that is required may vary widely.
* @param request current action request
* @param response current action response
* @param handler handler to use. This object must have previously been passed
* to the <code>supports</code> method of this interface, which must have
* returned true.
* @throws Exception in case of errors
*/
void handleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception;
/**
* Use the given handler to handle this render request.
* The workflow that is required may vary widely.
* @param request current render request
* @param response current render response
* @param handler handler to use. This object must have previously been passed
* to the <code>supports</code> method of this interface, which must have
* returned <code>true</code>.
* @throws Exception in case of errors
* @return ModelAndView object with the name of the view and the required
* model data, or <code>null</code> if the request has been handled directly
*/
ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception;
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
/**
* Interface to be implemented by objects than can resolve exceptions thrown
* during handler mapping or execution, in the typical case to error views.
* Implementors are typically registered as beans in the application context.
*
* <p>Error views are analogous to the error page JSPs, but can be used with
* any kind of exception including any checked exception, with potentially
* fine-granular mappings for specific handlers.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* @param request current portlet request
* @param response current portlet response
* @param handler the executed handler, or null if none chosen at the time of
* the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or null for default processing
*/
ModelAndView resolveException(
RenderRequest request, RenderResponse response, Object handler, Exception ex);
}

View File

@ -0,0 +1,117 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.CollectionUtils;
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
* Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see HandlerInterceptor
*/
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
private List interceptorList;
/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
*/
public HandlerExecutionChain(Object handler) {
this(handler, null);
}
/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
* @param interceptors the array of interceptors to apply
* (in the given order) before the handler itself executes
*/
public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
/**
* Return the handler object to execute.
* @return the handler object
*/
public Object getHandler() {
return this.handler;
}
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList();
this.interceptorList.add(interceptor);
}
public void addInterceptors(HandlerInterceptor[] interceptors) {
if (interceptors != null) {
initInterceptorList();
for (int i = 0; i < interceptors.length; i++) {
this.interceptorList.add(interceptors[i]);
}
}
}
private void initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList();
}
if (this.interceptors != null) {
for (int i = 0; i < this.interceptors.length; i++) {
this.interceptorList.add(this.interceptors[i]);
}
this.interceptors = null;
}
}
/**
* Return the array of interceptors to apply (in the given order).
* @return the array of HandlerInterceptors instances (may be <code>null</code>)
*/
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = (HandlerInterceptor[])
this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
/**
* Workflow interface that allows for customized handler execution chains.
* Applications can register any number of existing or custom interceptors
* for certain groups of handlers, to add common pre-processing behavior
* without needing to modify each handler implementation.
*
* <p>A <code>HandlerInterceptor</code> gets called before the appropriate
* {@link org.springframework.web.portlet.HandlerAdapter} triggers the
* execution of the handler itself. This mechanism can be used for a large
* field of preprocessing aspects, e.g. for authorization checks,
* or common handler behavior like locale or theme changes. Its main purpose
* is to permit the factoring out of otherwise repetitive handler code.
*
* <p>Typically an interceptor chain is defined per
* {@link org.springframework.web.portlet.HandlerMapping} bean, sharing its
* granularity. To be able to apply a certain interceptor chain to a group of
* handlers, one needs to map the desired handlers via one
* <code>HandlerMapping</code> bean. The interceptors themselves are defined as
* beans in the application context, referenced by the mapping bean definition
* via its
* {@link org.springframework.web.portlet.handler.AbstractHandlerMapping#setInterceptors "interceptors"}
* property (in XML: a &lt;list&gt; of &lt;ref&gt; elements).
*
* <p>A <code>HandlerInterceptor</code> is basically similar to a Servlet
* {@link javax.servlet.Filter}, but in contrast to the latter it allows
* custom pre-processing with the option to prohibit the execution of the handler
* itself, and custom post-processing. <code>Filters</code> are more powerful;
* for example they allow for exchanging the request and response objects that
* are handed down the chain. Note that a filter gets configured in
* <code>web.xml</code>, a <code>HandlerInterceptor</code> in the application context.
*
* <p>As a basic guideline, fine-grained handler-related pre-processing tasks are
* candidates for <code>HandlerInterceptor</code> implementations, especially
* factored-out common handler code and authorization checks. On the other hand,
* a <code>Filter</code> is well-suited for request content and view content
* handling, like multipart forms and GZIP compression. This typically shows when
* one needs to map the filter to certain content types (e.g. images), or to all
* requests.
*
* <p>Be aware that filters cannot be applied to portlet requests (they
* only operate on servlet requests), so for portlet requests interceptors are
* essential.
*
* <p>If we assume a "sunny day" request cycle (i.e. a request where nothing goes wrong
* and all is well), the workflow of a <code>HandlerInterceptor</code> will be as
* follows:
*
* <p><b>Action Request:</b><p>
* <ol>
* <li><code>DispatcherPortlet</code> maps the action request to a particular handler
* and assembles a handler execution chain consisting of the handler that
* is to be invoked and all of the <code>HandlerInterceptor</code>
* instances that apply to the request.</li>
* <li>{@link org.springframework.web.portlet.HandlerInterceptor#preHandleAction(javax.portlet.ActionRequest, javax.portlet.ActionResponse, Object) preHandleAction(..)}
* is called; if the invocation of this method returns <code>true</code> then
* this workflow continues</li>
* <li>The target handler handles the action request (via
* {@link org.springframework.web.portlet.HandlerAdapter#handleAction(javax.portlet.ActionRequest, javax.portlet.ActionResponse, Object) HandlerAdapter.handleAction(..)})</li>
* <li>{@link org.springframework.web.portlet.HandlerInterceptor#afterActionCompletion(javax.portlet.ActionRequest, javax.portlet.ActionResponse, Object, Exception) afterActionCompletion(..)}
* is called</li>
* </ol>
*
* <p><b>Render Request:</b><p>
* <ol>
* <li><code>DispatcherPortlet</code> maps the render request to a particular handler
* and assembles a handler execution chain consisting of the handler that
* is to be invoked and all of the <code>HandlerInterceptor</code>
* instances that apply to the request.</li>
* <li>{@link org.springframework.web.portlet.HandlerInterceptor#preHandleRender(javax.portlet.RenderRequest, javax.portlet.RenderResponse, Object) preHandleRender(..)}
* is called; if the invocation of this method returns <code>true</code> then
* this workflow continues</li>
* <li>The target handler handles the render request (via
* {@link org.springframework.web.portlet.HandlerAdapter#handleRender(javax.portlet.RenderRequest, javax.portlet.RenderResponse, Object) HandlerAdapter.handleRender(..)})</li>
* <li>{@link org.springframework.web.portlet.HandlerInterceptor#postHandleRender(javax.portlet.RenderRequest, javax.portlet.RenderResponse, Object, ModelAndView) postHandleRender(..)}
* is called</li>
* <li>If the <code>HandlerAdapter</code> returned a <code>ModelAndView</code>,
* then <code>DispatcherPortlet</code> renders the view accordingly
* <li>{@link org.springframework.web.portlet.HandlerInterceptor#afterRenderCompletion(javax.portlet.RenderRequest, javax.portlet.RenderResponse, Object, Exception) afterRenderCompletion(..)}
* is called</li>
* </ol>
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see HandlerExecutionChain#getInterceptors
* @see org.springframework.web.portlet.HandlerMapping
* @see org.springframework.web.portlet.handler.AbstractHandlerMapping#setInterceptors
* @see org.springframework.web.portlet.HandlerExecutionChain
*/
public interface HandlerInterceptor {
/**
* Intercept the execution of a handler in the action phase.
* <p>Called after a HandlerMapping determines an appropriate handler object
* to handle an {@link ActionRequest}, but before said HandlerAdapter actually
* invokes the handler.
* <p>{@link DispatcherPortlet} processes a handler in an execution chain,
* consisting of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically throwing an exception or writing a custom response.
* @param request current portlet action request
* @param response current portlet action response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return <code>true</code> if the execution chain should proceed with the
* next interceptor or the handler itself. Else, <code>DispatcherPortlet</code>
* assumes that this interceptor has already dealt with the response itself
* @throws Exception in case of errors
*/
boolean preHandleAction(ActionRequest request, ActionResponse response, Object handler)
throws Exception;
/**
* Callback after completion of request processing in the action phase, that is,
* after rendering the view. Will be called on any outcome of handler execution,
* thus allowing for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's
* {@link #preHandleAction(javax.portlet.ActionRequest, javax.portlet.ActionResponse, Object)}
* method has successfully completed and returned <code>true</code>!
* @param request current portlet action request
* @param response current portlet action response
* @param handler chosen handler to execute, for type and/or instance examination
* @param ex exception thrown on handler execution, if any (only included as
* additional context information for the case where a handler threw an exception;
* request execution may have failed even when this argument is <code>null</code>)
* @throws Exception in case of errors
*/
void afterActionCompletion(
ActionRequest request, ActionResponse response, Object handler, Exception ex)
throws Exception;
/**
* Intercept the execution of a handler in the render phase.
* <p>Called after a HandlerMapping determines an appropriate handler object
* to handle a {@link RenderRequest}, but before said HandlerAdapter actually
* invokes the handler.
* <p>{@link DispatcherPortlet} processes a handler in an execution chain,
* consisting of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically throwing an exception or writing a custom response.
* @param request current portlet render request
* @param response current portlet render response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return <code>true</code> if the execution chain should proceed with the
* next interceptor or the handler itself. Else, <code>DispatcherPortlet</code>
* assumes that this interceptor has already dealt with the response itself
* @throws Exception in case of errors
*/
boolean preHandleRender(RenderRequest request, RenderResponse response, Object handler)
throws Exception;
/**
* Intercept the execution of a handler in the render phase.
* <p>Called after a {@link HandlerAdapter} actually invoked the handler, but
* before the <code>DispatcherPortlet</code> renders the view. Can thus expose
* additional model objects to the view via the given {@link ModelAndView}.
* <p><code>DispatcherPortlet</code> processes a handler in an execution chain,
* consisting of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution, getting
* applied in inverse order of the execution chain.
* @param request current portlet render request
* @param response current portlet render response
* @param handler chosen handler to execute, for type and/or instance examination
* @param modelAndView the <code>ModelAndView</code> that the handler returned
* (can also be <code>null</code>)
* @throws Exception in case of errors
*/
void postHandleRender(
RenderRequest request, RenderResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allowing
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's
* {@link #preHandleRender(javax.portlet.RenderRequest, javax.portlet.RenderResponse, Object)}
* method has successfully completed and returned <code>true</code>!
* @param request current portlet render request
* @param response current portlet render response
* @param handler chosen handler to execute, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
void afterRenderCompletion(
RenderRequest request, RenderResponse response, Object handler, Exception ex)
throws Exception;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import javax.portlet.PortletRequest;
/**
* Interface to be implemented by objects that define a mapping between
* requests and handler objects.
*
* <p>This class can be implemented by application developers, although this is not
* necessary, as {@link org.springframework.web.portlet.handler.PortletModeHandlerMapping},
* {@link org.springframework.web.portlet.handler.ParameterHandlerMapping} and
* {@link org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping}
* are included in the framework. The first is the default if no HandlerMapping
* bean is registered in the portlet application context.
*
* <p>HandlerMapping implementations can support mapped interceptors but do not
* have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
* instance, optionally accompanied by some {@link HandlerInterceptor} instances.
* The DispatcherPortlet will first call each HandlerInterceptor's
* <code>preHandle</code> method in the given order, finally invoking the handler
* itself if all <code>preHandle</code> methods have returned <code>true</code>.
*
* <p>The ability to parameterize this mapping is a powerful and unusual
* capability of this Portlet MVC framework. For example, it is possible to
* write a custom mapping based on session state, cookie state or many other
* variables. No other MVC framework seems to be equally flexible.
*
* <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
* interface to be able to specify a sorting order and thus a priority for getting
* applied by DispatcherPortlet. Non-Ordered instances get treated as lowest priority.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @see org.springframework.core.Ordered
* @see org.springframework.web.portlet.handler.AbstractHandlerMapping
* @see org.springframework.web.portlet.handler.PortletModeHandlerMapping
* @see org.springframework.web.portlet.handler.ParameterHandlerMapping
* @see org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping
*/
public interface HandlerMapping {
/**
* Return a handler and any interceptors for this request. The choice may be made
* on portlet mode, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns <code>null</code> if no match was found. This is not an error.
* The DispatcherPortlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current portlet request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or null if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(PortletRequest request) throws Exception;
}

View File

@ -0,0 +1,303 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import java.util.Map;
import org.springframework.ui.ModelMap;
/**
* Holder for both Model and View in the web MVC framework.
* Note that these are entirely distinct. This class merely holds
* both to make it possible for a controller to return both model
* and view in a single return value.
*
* <p>Represents a model and view returned by a handler, to be resolved
* by a DispatcherPortlet. The view can take the form of a String
* view name which will need to be resolved by a ViewResolver object;
* alternatively a view object can be specified directly. The model
* is a Map, allowing the use of multiple objects keyed by name.
*
* @author Juergen Hoeller
* @since 2.0
* @see org.springframework.web.portlet.DispatcherPortlet
* @see org.springframework.web.servlet.ViewResolver
* @see org.springframework.web.portlet.HandlerAdapter
* @see org.springframework.web.portlet.mvc.Controller
*/
public class ModelAndView {
/** View instance or view name String */
private Object view;
/** Model Map */
private ModelMap model;
/**
* Indicates whether or not this instance has been cleared with a call to {@link #clear()}.
*/
private boolean cleared;
/**
* Default constructor for bean-style usage: populating bean
* properties instead of passing in constructor arguments.
* @see #setView(Object)
* @see #setViewName(String)
*/
public ModelAndView() {
}
/**
* Convenient constructor when there is no model data to expose.
* Can also be used in conjunction with <code>addObject</code>.
* @param viewName name of the View to render, to be resolved
* by the DispatcherPortlet's ViewResolver
* @see #addObject
*/
public ModelAndView(String viewName) {
this.view = viewName;
}
/**
* Convenient constructor when there is no model data to expose.
* Can also be used in conjunction with <code>addObject</code>.
* @param view View object to render (usually a Servlet MVC View object)
* @see #addObject
*/
public ModelAndView(Object view) {
this.view = view;
}
/**
* Create a new ModelAndView given a view name and a model.
* @param viewName name of the View to render, to be resolved
* by the DispatcherPortlet's ViewResolver
* @param model Map of model names (Strings) to model objects
* (Objects). Model entries may not be <code>null</code>, but the
* model Map may be <code>null</code> if there is no model data.
*/
public ModelAndView(String viewName, Map model) {
this.view = viewName;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
/**
* Create a new ModelAndView given a View object and a model.
* @param view View object to render (usually a Servlet MVC View object)
* @param model Map of model names (Strings) to model objects
* (Objects). Model entries may not be <code>null</code>, but the
* model Map may be <code>null</code> if there is no model data.
*/
public ModelAndView(Object view, Map model) {
this.view = view;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
/**
* Convenient constructor to take a single model object.
* @param viewName name of the View to render, to be resolved
* by the DispatcherPortlet's ViewResolver
* @param modelName name of the single entry in the model
* @param modelObject the single model object
*/
public ModelAndView(String viewName, String modelName, Object modelObject) {
this.view = viewName;
addObject(modelName, modelObject);
}
/**
* Convenient constructor to take a single model object.
* @param view View object to render (usually a Servlet MVC View object)
* @param modelName name of the single entry in the model
* @param modelObject the single model object
*/
public ModelAndView(Object view, String modelName, Object modelObject) {
this.view = view;
addObject(modelName, modelObject);
}
/**
* Set a view name for this ModelAndView, to be resolved by the
* DispatcherPortlet via a ViewResolver. Will override any
* pre-existing view name or View.
*/
public void setViewName(String viewName) {
this.view = viewName;
}
/**
* Return the view name to be resolved by the DispatcherPortlet
* via a ViewResolver, or <code>null</code> if we are using a view object.
*/
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
/**
* Set a View object for this ModelAndView. Will override any
* pre-existing view name or View.
* <p>The given View object will usually be a Servlet MVC View object.
* This is nevertheless typed as Object to avoid a Servlet API dependency
* in the Portlet ModelAndView class.
*/
public void setView(Object view) {
this.view = view;
}
/**
* Return the View object, or <code>null</code> if we are using a view name
* to be resolved by the DispatcherPortlet via a ViewResolver.
*/
public Object getView() {
return (!(this.view instanceof String) ? this.view : null);
}
/**
* Indicate whether or not this <code>ModelAndView</code> has a view, either
* as a view name or as a direct view instance.
*/
public boolean hasView() {
return (this.view != null);
}
/**
* Return whether we use a view reference, i.e. <code>true</code>
* if the view has been specified via a name to be resolved by the
* DispatcherPortlet via a ViewResolver.
*/
public boolean isReference() {
return (this.view instanceof String);
}
/**
* Return the model map. May return <code>null</code>.
* Called by DispatcherPortlet for evaluation of the model.
*/
protected Map getModelInternal() {
return this.model;
}
/**
* Return the underlying <code>ModelMap</code> instance (never <code>null</code>).
*/
public ModelMap getModelMap() {
if (this.model == null) {
this.model = new ModelMap();
}
return this.model;
}
/**
* Return the model map. Never returns <code>null</code>.
* To be called by application code for modifying the model.
*/
public Map getModel() {
return getModelMap();
}
/**
* Add an attribute to the model.
* @param attributeName name of the object to add to the model
* @param attributeValue object to add to the model (never <code>null</code>)
* @see ModelMap#addAttribute(String, Object)
* @see #getModelMap()
*/
public ModelAndView addObject(String attributeName, Object attributeValue) {
getModelMap().addAttribute(attributeName, attributeValue);
return this;
}
/**
* Add an attribute to the model using parameter name generation.
* @param attributeValue the object to add to the model (never <code>null</code>)
* @see ModelMap#addAttribute(Object)
* @see #getModelMap()
*/
public ModelAndView addObject(Object attributeValue) {
getModelMap().addAttribute(attributeValue);
return this;
}
/**
* Add all attributes contained in the provided Map to the model.
* @param modelMap a Map of attributeName -> attributeValue pairs
* @see ModelMap#addAllAttributes(Map)
* @see #getModelMap()
*/
public ModelAndView addAllObjects(Map modelMap) {
getModelMap().addAllAttributes(modelMap);
return this;
}
/**
* Clear the state of this ModelAndView object.
* The object will be empty afterwards.
* <p>Can be used to suppress rendering of a given ModelAndView object
* in the <code>postHandleRender</code> method of a HandlerInterceptor.
* @see #isEmpty()
* @see HandlerInterceptor#postHandleRender
*/
public void clear() {
this.view = null;
this.model = null;
this.cleared = true;
}
/**
* Return whether this ModelAndView object is empty
* i.e. whether it does not hold any view and does not contain a model.
*/
public boolean isEmpty() {
return (this.view == null && this.model == null);
}
/**
* Return whether this ModelAndView object is empty as a result of a call to {@link #clear}
* i.e. whether it does not hold any view and does not contain a model.
* Returns <code>false</code> if any additional state was added to the instance
* <strong>after</strong> the call to {@link #clear}.
* @see #clear()
*/
public boolean wasCleared() {
return (this.cleared && isEmpty());
}
/**
* Return diagnostic information about this model and view.
*/
public String toString() {
StringBuffer buf = new StringBuffer("ModelAndView: ");
if (isReference()) {
buf.append("reference to view with name '").append(this.view).append("'");
}
else {
buf.append("materialized View is [").append(this.view).append(']');
}
buf.append("; model is ").append(this.model);
return buf.toString();
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet;
import javax.portlet.PortletException;
/**
* Exception to be thrown on error conditions that should forward
* to a specific view with a specific model.
*
* <p>Can be thrown at any time during handler processing.
* This includes any template methods of pre-built controllers.
* For example, a form controller might abort to a specific error page
* if certain parameters do not allow to proceed with the normal workflow.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class ModelAndViewDefiningException extends PortletException {
private ModelAndView modelAndView;
/**
* Create new ModelAndViewDefiningException with the given ModelAndView,
* typically representing a specific error page.
* @param modelAndView ModelAndView with view to forward to and model to expose
*/
public ModelAndViewDefiningException(ModelAndView modelAndView) {
if (modelAndView == null) {
throw new IllegalArgumentException("modelAndView must not be null in ModelAndViewDefiningException");
}
this.modelAndView = modelAndView;
}
/**
* Return the ModelAndView that this exception contains for forwarding to.
*/
public ModelAndView getModelAndView() {
return modelAndView;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.bind;
/**
* PortletRequestBindingException subclass that indicates a missing parameter.
*
* @author Juergen Hoeller
* @since 2.0.2
*/
public class MissingPortletRequestParameterException extends PortletRequestBindingException {
private String parameterName;
private String parameterType;
/**
* Constructor for MissingPortletRequestParameterException.
* @param parameterName the name of the missing parameter
* @param parameterType the expected type of the missing parameter
*/
public MissingPortletRequestParameterException(String parameterName, String parameterType) {
super("");
this.parameterName = parameterName;
this.parameterType = parameterType;
}
public String getMessage() {
return "Required " + this.parameterType + " parameter '" + parameterName + "' is not present";
}
/**
* Return the name of the offending parameter.
*/
public String getParameterName() {
return this.parameterName;
}
/**
* Return the expected type of the offending parameter.
*/
public String getParameterType() {
return this.parameterType;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.bind;
import javax.portlet.PortletException;
/**
* Fatal binding exception, thrown when we want to
* treat binding exceptions as unrecoverable.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public class PortletRequestBindingException extends PortletException {
/**
* Constructor for PortletRequestBindingException.
* @param msg the detail message
*/
public PortletRequestBindingException(String msg) {
super(msg);
}
/**
* Constructor for PortletRequestBindingException.
* @param msg the detail message
* @param cause the root cause
*/
public PortletRequestBindingException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.bind;
import javax.portlet.PortletRequest;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.BindException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.portlet.multipart.MultipartActionRequest;
/**
* Special {@link org.springframework.validation.DataBinder} to perform data binding
* from portlet request parameters to JavaBeans, including support for multipart files.
*
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
*
* <p>Used by Spring Portlet MVC's BaseCommandController.
* Note that BaseCommandController and its subclasses allow for easy customization
* of the binder instances that they use through overriding <code>initBinder</code>.
*
* <p>Can also be used for manual data binding in custom web controllers:
* for example, in a plain Portlet Controller implementation. Simply instantiate
* a PortletRequestDataBinder for each binding process, and invoke <code>bind</code>
* with the current PortletRequest as argument:
*
* <pre class="code">
* MyBean myBean = new MyBean();
* // apply binder to custom target object
* PortletRequestDataBinder binder = new PortletRequestDataBinder(myBean);
* // register custom editors, if desired
* binder.registerCustomEditor(...);
* // trigger actual binding of request parameters
* binder.bind(request);
* // optionally evaluate binding errors
* Errors errors = binder.getErrors();
* ...</pre>
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #bind(javax.portlet.PortletRequest)
* @see #registerCustomEditor
* @see #setAllowedFields
* @see #setRequiredFields
* @see #setFieldMarkerPrefix
* @see org.springframework.web.portlet.mvc.BaseCommandController#initBinder
*/
public class PortletRequestDataBinder extends WebDataBinder {
/**
* Create a new PortletRequestDataBinder instance, with default object name.
* @param target the target object to bind onto (or <code>null</code>
* if the binder is just used to convert a plain parameter value)
* @see #DEFAULT_OBJECT_NAME
*/
public PortletRequestDataBinder(Object target) {
super(target);
}
/**
* Create a new PortletRequestDataBinder instance.
* @param target the target object to bind onto (or <code>null</code>
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public PortletRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
/**
* Bind the parameters of the given request to this binder's target,
* also binding multipart files in case of a multipart request.
* <p>This call can create field errors, representing basic binding
* errors like a required field (code "required"), or type mismatch
* between value and bean property (code "typeMismatch").
* <p>Multipart files are bound via their parameter name, just like normal
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
* invoking a "setUploadedFile" setter method.
* <p>The type of the target property for a multipart file can be MultipartFile,
* byte[], or String. The latter two receive the contents of the uploaded file;
* all metadata like original file name, content type, etc are lost in those cases.
* @param request request with parameters to bind (can be multipart)
* @see org.springframework.web.portlet.multipart.MultipartActionRequest
* @see org.springframework.web.multipart.MultipartFile
* @see #bindMultipartFiles
* @see #bind(org.springframework.beans.PropertyValues)
*/
public void bind(PortletRequest request) {
MutablePropertyValues mpvs = new PortletRequestParameterPropertyValues(request);
if (request instanceof MultipartActionRequest) {
MultipartActionRequest multipartRequest = (MultipartActionRequest) request;
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
}
doBind(mpvs);
}
/**
* Treats errors as fatal.
* <p>Use this method only if it's an error if the input isn't valid.
* This might be appropriate if all input is from dropdowns, for example.
* @throws PortletRequestBindingException subclass of PortletException on any binding problem
*/
public void closeNoCatch() throws PortletRequestBindingException {
if (getBindingResult().hasErrors()) {
throw new PortletRequestBindingException(
"Errors binding onto object '" + getBindingResult().getObjectName() + "'",
new BindException(getBindingResult()));
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.bind;
import javax.portlet.PortletRequest;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.web.portlet.util.PortletUtils;
/**
* PropertyValues implementation created from parameters in a PortletRequest.
* Can look for all property values beginning with a certain prefix and
* prefix separator (default is "_").
*
* <p>For example, with a prefix of "spring", "spring_param1" and
* "spring_param2" result in a Map with "param1" and "param2" as keys.
*
* <p>This class is not immutable to be able to efficiently remove property
* values that should be ignored for binding.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.util.PortletUtils#getParametersStartingWith
*/
public class PortletRequestParameterPropertyValues extends MutablePropertyValues {
/** Default prefix separator */
public static final String DEFAULT_PREFIX_SEPARATOR = "_";
/**
* Create new PortletRequestPropertyValues using no prefix
* (and hence, no prefix separator).
* @param request portlet request
*/
public PortletRequestParameterPropertyValues(PortletRequest request) {
this(request, null, null);
}
/**
* Create new PortletRequestPropertyValues using the given prefix and
* the default prefix separator (the underscore character "_").
* @param request portlet request
* @param prefix the prefix for parameters (the full prefix will
* consist of this plus the separator)
* @see #DEFAULT_PREFIX_SEPARATOR
*/
public PortletRequestParameterPropertyValues(PortletRequest request, String prefix) {
this(request, prefix, DEFAULT_PREFIX_SEPARATOR);
}
/**
* Create new PortletRequestPropertyValues supplying both prefix and
* prefix separator.
* @param request portlet request
* @param prefix the prefix for parameters (the full prefix will
* consist of this plus the separator)
* @param prefixSeparator separator delimiting prefix (e.g. "spring")
* and the rest of the parameter name ("param1", "param2")
*/
public PortletRequestParameterPropertyValues(PortletRequest request, String prefix, String prefixSeparator) {
super(PortletUtils.getParametersStartingWith(
request, (prefix != null) ? prefix + prefixSeparator : null));
}
}

View File

@ -0,0 +1,699 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.bind;
import javax.portlet.PortletRequest;
/**
* Parameter extraction methods, for an approach distinct from data binding,
* in which parameters of specific types are required.
*
* <p>This approach is very useful for simple submissions, where binding
* request parameters to a command object would be overkill.
*
* @author Juergen Hoeller
* @author Keith Donald
* @author John A. Lewis
* @since 2.0
*/
public abstract class PortletRequestUtils {
private static final IntParser INT_PARSER = new IntParser();
private static final LongParser LONG_PARSER = new LongParser();
private static final FloatParser FLOAT_PARSER = new FloatParser();
private static final DoubleParser DOUBLE_PARSER = new DoubleParser();
private static final BooleanParser BOOLEAN_PARSER = new BooleanParser();
private static final StringParser STRING_PARSER = new StringParser();
/**
* Get an Integer parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @return the Integer value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static Integer getIntParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Integer(getRequiredIntParameter(request, name));
}
/**
* Get an int parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static int getIntParameter(PortletRequest request, String name, int defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredIntParameter(request, name);
}
catch (PortletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of int parameters, return an empty array if not found.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static int[] getIntParameters(PortletRequest request, String name) {
try {
return getRequiredIntParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new int[0];
}
}
/**
* Get an int parameter, throwing an exception if it isn't found or isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static int getRequiredIntParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return INT_PARSER.parseInt(name, request.getParameter(name));
}
/**
* Get an array of int parameters, throwing an exception if not found or one is not a number..
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static int[] getRequiredIntParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return INT_PARSER.parseInts(name, request.getParameterValues(name));
}
/**
* Get a Long parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @return the Long value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static Long getLongParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Long(getRequiredLongParameter(request, name));
}
/**
* Get a long parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static long getLongParameter(PortletRequest request, String name, long defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredLongParameter(request, name);
}
catch (PortletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of long parameters, return an empty array if not found.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static long[] getLongParameters(PortletRequest request, String name) {
try {
return getRequiredLongParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new long[0];
}
}
/**
* Get a long parameter, throwing an exception if it isn't found or isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static long getRequiredLongParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return LONG_PARSER.parseLong(name, request.getParameter(name));
}
/**
* Get an array of long parameters, throwing an exception if not found or one is not a number.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static long[] getRequiredLongParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return LONG_PARSER.parseLongs(name, request.getParameterValues(name));
}
/**
* Get a Float parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @return the Float value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static Float getFloatParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Float(getRequiredFloatParameter(request, name));
}
/**
* Get a float parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static float getFloatParameter(PortletRequest request, String name, float defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredFloatParameter(request, name);
}
catch (PortletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of float parameters, return an empty array if not found.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static float[] getFloatParameters(PortletRequest request, String name) {
try {
return getRequiredFloatParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new float[0];
}
}
/**
* Get a float parameter, throwing an exception if it isn't found or isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static float getRequiredFloatParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return FLOAT_PARSER.parseFloat(name, request.getParameter(name));
}
/**
* Get an array of float parameters, throwing an exception if not found or one is not a number.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static float[] getRequiredFloatParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return FLOAT_PARSER.parseFloats(name, request.getParameterValues(name));
}
/**
* Get a Double parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @return the Double value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static Double getDoubleParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Double(getRequiredDoubleParameter(request, name));
}
/**
* Get a double parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static double getDoubleParameter(PortletRequest request, String name, double defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredDoubleParameter(request, name);
}
catch (PortletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of double parameters, return an empty array if not found.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static double[] getDoubleParameters(PortletRequest request, String name) {
try {
return getRequiredDoubleParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new double[0];
}
}
/**
* Get a double parameter, throwing an exception if it isn't found or isn't a number.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static double getRequiredDoubleParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return DOUBLE_PARSER.parseDouble(name, request.getParameter(name));
}
/**
* Get an array of double parameters, throwing an exception if not found or one is not a number.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static double[] getRequiredDoubleParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return DOUBLE_PARSER.parseDoubles(name, request.getParameterValues(name));
}
/**
* Get a Boolean parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a boolean.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current portlet request
* @param name the name of the parameter
* @return the Boolean value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static Boolean getBooleanParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return (getRequiredBooleanParameter(request, name) ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Get a boolean parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static boolean getBooleanParameter(PortletRequest request, String name, boolean defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredBooleanParameter(request, name);
}
catch (PortletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of boolean parameters, return an empty array if not found.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static boolean[] getBooleanParameters(PortletRequest request, String name) {
try {
return getRequiredBooleanParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new boolean[0];
}
}
/**
* Get a boolean parameter, throwing an exception if it isn't found
* or isn't a boolean.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static boolean getRequiredBooleanParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return BOOLEAN_PARSER.parseBoolean(name, request.getParameter(name));
}
/**
* Get an array of boolean parameters, throwing an exception if not found
* or one isn't a boolean.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static boolean[] getRequiredBooleanParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return BOOLEAN_PARSER.parseBooleans(name, request.getParameterValues(name));
}
/**
* Get a String parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value is empty.
* @param request current portlet request
* @param name the name of the parameter
* @return the String value, or <code>null</code> if not present
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static String getStringParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return getRequiredStringParameter(request, name);
}
/**
* Get a String parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value to default to enable checks of whether it was supplied.
* @param request current portlet request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static String getStringParameter(PortletRequest request, String name, String defaultVal) {
String val = request.getParameter(name);
return (val != null ? val : defaultVal);
}
/**
* Get an array of String parameters, return an empty array if not found.
* @param request current portlet request
* @param name the name of the parameter with multiple possible values
*/
public static String[] getStringParameters(PortletRequest request, String name) {
try {
return getRequiredStringParameters(request, name);
}
catch (PortletRequestBindingException ex) {
return new String[0];
}
}
/**
* Get a String parameter, throwing an exception if it isn't found or is empty.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static String getRequiredStringParameter(PortletRequest request, String name)
throws PortletRequestBindingException {
return STRING_PARSER.validateRequiredString(name, request.getParameter(name));
}
/**
* Get an array of String parameters, throwing an exception if not found or one is empty.
* @param request current portlet request
* @param name the name of the parameter
* @throws PortletRequestBindingException a subclass of PortletException,
* so it doesn't need to be caught
*/
public static String[] getRequiredStringParameters(PortletRequest request, String name)
throws PortletRequestBindingException {
return STRING_PARSER.validateRequiredStrings(name, request.getParameterValues(name));
}
private abstract static class ParameterParser {
protected final Object parse(String name, String parameter) throws PortletRequestBindingException {
validateRequiredParameter(name, parameter);
try {
return doParse(parameter);
}
catch (NumberFormatException ex) {
throw new PortletRequestBindingException(
"Required " + getType() + " parameter '" + name + "' with value of '" +
parameter + "' is not a valid number", ex);
}
}
protected final void validateRequiredParameter(String name, Object parameter)
throws PortletRequestBindingException {
if (parameter == null) {
throw new MissingPortletRequestParameterException(name, getType());
}
}
protected abstract String getType();
protected abstract Object doParse(String parameter) throws NumberFormatException;
}
private static class IntParser extends ParameterParser {
protected String getType() {
return "int";
}
protected Object doParse(String s) throws NumberFormatException {
return Integer.valueOf(s);
}
public int parseInt(String name, String parameter) throws PortletRequestBindingException {
return ((Number) parse(name, parameter)).intValue();
}
public int[] parseInts(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
int[] parameters = new int[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseInt(name, values[i]);
}
return parameters;
}
}
private static class LongParser extends ParameterParser {
protected String getType() {
return "long";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Long.valueOf(parameter);
}
public long parseLong(String name, String parameter) throws PortletRequestBindingException {
return ((Number) parse(name, parameter)).longValue();
}
public long[] parseLongs(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
long[] parameters = new long[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseLong(name, values[i]);
}
return parameters;
}
}
private static class FloatParser extends ParameterParser {
protected String getType() {
return "float";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Float.valueOf(parameter);
}
public float parseFloat(String name, String parameter) throws PortletRequestBindingException {
return ((Number) parse(name, parameter)).floatValue();
}
public float[] parseFloats(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
float[] parameters = new float[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseFloat(name, values[i]);
}
return parameters;
}
}
private static class DoubleParser extends ParameterParser {
protected String getType() {
return "double";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Double.valueOf(parameter);
}
public double parseDouble(String name, String parameter) throws PortletRequestBindingException {
return ((Number) parse(name, parameter)).doubleValue();
}
public double[] parseDoubles(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
double[] parameters = new double[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseDouble(name, values[i]);
}
return parameters;
}
}
private static class BooleanParser extends ParameterParser {
protected String getType() {
return "boolean";
}
protected Object doParse(String parameter) throws NumberFormatException {
return (parameter.equalsIgnoreCase("true") || parameter.equalsIgnoreCase("on") ||
parameter.equalsIgnoreCase("yes") || parameter.equals("1") ? Boolean.TRUE : Boolean.FALSE);
}
public boolean parseBoolean(String name, String parameter) throws PortletRequestBindingException {
return ((Boolean) parse(name, parameter)).booleanValue();
}
public boolean[] parseBooleans(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
boolean[] parameters = new boolean[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseBoolean(name, values[i]);
}
return parameters;
}
}
private static class StringParser extends ParameterParser {
protected String getType() {
return "string";
}
protected Object doParse(String parameter) throws NumberFormatException {
return parameter;
}
public String validateRequiredString(String name, String value) throws PortletRequestBindingException {
validateRequiredParameter(name, value);
return value;
}
public String[] validateRequiredStrings(String name, String[] values) throws PortletRequestBindingException {
validateRequiredParameter(name, values);
for (int i = 0; i < values.length; i++) {
validateRequiredParameter(name, values[i]);
}
return values;
}
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Provides portlet-specific data binding functionality.
</body>
</html>

View File

@ -0,0 +1,166 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractRefreshableConfigApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.ServletContextAwareProcessor;
/**
* {@link org.springframework.context.support.AbstractRefreshableApplicationContext}
* subclass which implements the {@link ConfigurablePortletApplicationContext}
* interface for portlet environments. Provides a "configLocations" property,
* to be populated through the ConfigurablePortletApplicationContext interface
* on portlet application startup.
*
* <p>This class is as easy to subclass as AbstractRefreshableApplicationContext:
* All you need to implements is the {@link #loadBeanDefinitions} method;
* see the superclass javadoc for details. Note that implementations are supposed
* to load bean definitions from the files specified by the locations returned
* by the {@link #getConfigLocations} method.
*
* <p>Interprets resource paths as servlet context resources, i.e. as paths beneath
* the web application root. Absolute paths, e.g. for files outside the web app root,
* can be accessed via "file:" URLs, as implemented by
* {@link org.springframework.core.io.DefaultResourceLoader}.
*
* <p><b>This is the portlet context to be subclassed for a different bean definition format.</b>
* Such a context implementation can be specified as "contextClass" init-param
* for FrameworkPortlet, replacing the default {@link XmlPortletApplicationContext}.
* It will then automatically receive the "contextConfigLocation" init-param.
*
* <p>Note that Portlet-based context implementations are generally supposed
* to configure themselves based on the configuration received through the
* {@link ConfigurablePortletApplicationContext} interface. In contrast, a standalone
* application context might allow for configuration in custom startup code
* (for example, {@link org.springframework.context.support.GenericApplicationContext}).
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #loadBeanDefinitions
* @see org.springframework.web.portlet.context.ConfigurablePortletApplicationContext#setConfigLocations
* @see XmlPortletApplicationContext
*/
public abstract class AbstractRefreshablePortletApplicationContext extends AbstractRefreshableConfigApplicationContext
implements WebApplicationContext, ConfigurablePortletApplicationContext {
/** Servlet context that this context runs in */
private ServletContext servletContext;
/** Portlet context that this context runs in */
private PortletContext portletContext;
/** Portlet config that this context runs in */
private PortletConfig portletConfig;
/** Namespace of this context, or null if root */
private String namespace;
public AbstractRefreshablePortletApplicationContext() {
setDisplayName("Root PortletApplicationContext");
}
public void setParent(ApplicationContext parent) {
super.setParent(parent);
if (parent instanceof WebApplicationContext) {
this.servletContext = ((WebApplicationContext) parent).getServletContext();
}
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setPortletContext(PortletContext portletContext) {
this.portletContext = portletContext;
}
public PortletContext getPortletContext() {
return this.portletContext;
}
public void setPortletConfig(PortletConfig portletConfig) {
this.portletConfig = portletConfig;
if (portletConfig != null && this.portletContext == null) {
this.portletContext = portletConfig.getPortletContext();
}
}
public PortletConfig getPortletConfig() {
return this.portletConfig;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
if (namespace != null) {
setDisplayName("PortletApplicationContext for namespace '" + namespace + "'");
}
}
public String getNamespace() {
return this.namespace;
}
public String[] getConfigLocations() {
return super.getConfigLocations();
}
/**
* Register request/session scopes, a {@link PortletContextAwareProcessor}, etc.
*/
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext));
beanFactory.addBeanPostProcessor(new PortletContextAwareProcessor(this.portletContext, this.portletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(PortletContextAware.class);
beanFactory.ignoreDependencyInterface(PortletConfigAware.class);
beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext);
beanFactory.registerResolvableDependency(PortletContext.class, this.portletContext);
beanFactory.registerResolvableDependency(PortletConfig.class, this.portletConfig);
PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory);
}
/**
* This implementation supports file paths beneath the root of the PortletContext.
* @see PortletContextResource
*/
protected Resource getResourceByPath(String path) {
return new PortletContextResource(this.portletContext, path);
}
/**
* This implementation supports pattern matching in unexpanded WARs too.
* @see PortletContextResourcePatternResolver
*/
protected ResourcePatternResolver getResourcePatternResolver() {
return new PortletContextResourcePatternResolver(this);
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/**
* Interface to be implemented by configurable portlet application contexts.
* Supported by {@link org.springframework.web.portlet.FrameworkPortlet}.
*
* <p>Note: The setters of this interface need to be called before an
* invocation of the {@link #refresh} method inherited from
* {@link org.springframework.context.ConfigurableApplicationContext}.
* They do not cause an initialization of the context on their own.
*
* @author Juergen Hoeller
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @since 2.0
* @see #refresh
* @see org.springframework.web.context.ContextLoader#createWebApplicationContext
* @see org.springframework.web.portlet.FrameworkPortlet#createPortletApplicationContext
* @see org.springframework.web.context.ConfigurableWebApplicationContext
*/
public interface ConfigurablePortletApplicationContext
extends WebApplicationContext, ConfigurableApplicationContext {
/**
* Set the PortletContext for this portlet application context.
* <p>Does not cause an initialization of the context: refresh needs to be
* called after the setting of all configuration properties.
* @see #refresh()
*/
void setPortletContext(PortletContext portletContext);
/**
* Return the standard Portlet API PortletContext for this application.
*/
PortletContext getPortletContext();
/**
* Set the PortletConfig for this portlet application context.
* @see #refresh()
*/
void setPortletConfig(PortletConfig portletConfig);
/**
* Return the PortletConfig for this portlet application context, if any.
*/
PortletConfig getPortletConfig();
/**
* Set the namespace for this portlet application context,
* to be used for building a default context config location.
*/
void setNamespace(String namespace);
/**
* Return the namespace for this web application context, if any.
*/
String getNamespace();
/**
* Set the config locations for this portlet application context in init-param style,
* i.e. with distinct locations separated by commas, semicolons or whitespace.
* <p>If not set, the implementation is supposed to use a default for the
* given namespace.
*/
void setConfigLocation(String configLocation);
/**
* Set the config locations for this portlet application context.
* <p>If not set, the implementation is supposed to use a default for the
* given namespace.
*/
void setConfigLocations(String[] configLocations);
/**
* Return the config locations for this web application context,
* or <code>null</code> if none specified.
*/
String[] getConfigLocations();
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.context.request.SessionScope;
/**
* Convenience methods for retrieving the root WebApplicationContext for a given
* PortletContext. This is e.g. useful for accessing a Spring context from
* within custom Portlet implementations.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.context.ContextLoader
* @see org.springframework.web.context.support.WebApplicationContextUtils
* @see org.springframework.web.portlet.FrameworkPortlet
* @see org.springframework.web.portlet.DispatcherPortlet
*/
public abstract class PortletApplicationContextUtils {
/**
* Find the root WebApplicationContext for this portlet application, which is
* typically loaded via ContextLoaderListener or ContextLoaderServlet.
* <p>Will rethrow an exception that happened on root context startup,
* to differentiate between a failed context startup and no context at all.
* @param pc PortletContext to find the web application context for
* @return the root WebApplicationContext for this web app, or <code>null</code> if none
* (typed to ApplicationContext to avoid a Servlet API dependency; can usually
* be casted to WebApplicationContext, but there shouldn't be a need to)
* @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
public static ApplicationContext getWebApplicationContext(PortletContext pc) {
Assert.notNull(pc, "PortletContext must not be null");
Object attr = pc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (!(attr instanceof ApplicationContext)) {
throw new IllegalStateException("Root context attribute is not of type WebApplicationContext: " + attr);
}
return (ApplicationContext) attr;
}
/**
* Find the root WebApplicationContext for this portlet application, which is
* typically loaded via ContextLoaderListener or ContextLoaderServlet.
* <p>Will rethrow an exception that happened on root context startup,
* to differentiate between a failed context startup and no context at all.
* @param pc PortletContext to find the web application context for
* @return the root WebApplicationContext for this web app
* (typed to ApplicationContext to avoid a Servlet API dependency; can usually
* be casted to WebApplicationContext, but there shouldn't be a need to)
* @throws IllegalStateException if the root WebApplicationContext could not be found
* @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
public static ApplicationContext getRequiredWebApplicationContext(PortletContext pc)
throws IllegalStateException {
ApplicationContext wac = getWebApplicationContext(pc);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
return wac;
}
/**
* Register portlet-specific scopes with the given BeanFactory,
* as used by the Portlet ApplicationContext.
* @param beanFactory the BeanFactory to configure
*/
static void registerPortletApplicationScopes(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
beanFactory.registerResolvableDependency(PortletRequest.class, new ObjectFactory() {
public Object getObject() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof PortletRequestAttributes)) {
throw new IllegalStateException("Current request is not a portlet request");
}
return ((PortletRequestAttributes) requestAttr).getRequest();
}
});
beanFactory.registerResolvableDependency(PortletSession.class, new ObjectFactory() {
public Object getObject() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof PortletRequestAttributes)) {
throw new IllegalStateException("Current request is not a portlet request");
}
return ((PortletRequestAttributes) requestAttr).getRequest().getPortletSession();
}
});
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.io.File;
import javax.portlet.PortletContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.web.portlet.util.PortletUtils;
/**
* Convenient superclass for application objects running in a Portlet ApplicationContext.
* Provides getApplicationContext, getServletContext, and getTempDir methods.
*
* @author Juergen Hoeller
* @since 2.0
*/
public abstract class PortletApplicationObjectSupport extends ApplicationObjectSupport
implements PortletContextAware {
private PortletContext portletContext;
public void setPortletContext(PortletContext portletContext) {
this.portletContext = portletContext;
}
/**
* Overrides the base class behavior to enforce running in an ApplicationContext.
* All accessors will throw IllegalStateException if not running in a context.
* @see #getApplicationContext()
* @see #getMessageSourceAccessor()
* @see #getPortletContext()
* @see #getTempDir()
*/
protected boolean isContextRequired() {
return true;
}
/**
* Return the current PortletContext.
* @throws IllegalStateException if not running within a PortletContext
*/
protected final PortletContext getPortletContext() throws IllegalStateException {
if (this.portletContext == null) {
throw new IllegalStateException(
"PortletApplicationObjectSupport instance [" + this + "] does not run within a PortletContext");
}
return this.portletContext;
}
/**
* Return the temporary directory for the current web application,
* as provided by the servlet container.
* @return the File representing the temporary directory
* @throws IllegalStateException if not running within a PortletContext
* @see org.springframework.web.portlet.util.PortletUtils#getTempDir(javax.portlet.PortletContext)
*/
protected final File getTempDir() throws IllegalStateException {
return PortletUtils.getTempDir(getPortletContext());
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletConfig;
/**
* Interface to be implemented by any object that wishes to be notified
* of the PortletConfig (typically determined by the PortletApplicationContext)
* that it runs in.
*
* @author Juergen Hoeller
* @since 2.0
* @see PortletContextAware
*/
public interface PortletConfigAware {
/**
* Set the PortletConfigthat this object runs in.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's afterPropertiesSet or a custom init-method.
* Invoked after ApplicationContextAware's setApplicationContext.
* @param portletConfig PortletConfig object to be used by this object
*/
void setPortletConfig(PortletConfig portletConfig);
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletContext;
/**
* Interface to be implemented by any object that wishes to be notified
* of the PortletContext (typically determined by the PortletApplicationContext)
* that it runs in.
*
* @author Juergen Hoeller
* @author William G. Thompson, Jr.
* @since 2.0
* @see PortletConfigAware
*/
public interface PortletContextAware {
/**
* Set the PortletContext that this object runs in.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's afterPropertiesSet or a custom init-method.
* Invoked after ApplicationContextAware's setApplicationContext.
* @param portletContext PortletContext object to be used by this object
*/
void setPortletContext(PortletContext portletContext);
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor}
* implementation that passes the PortletContext to beans that implement
* the {@link PortletContextAware} interface.
*
* <p>Portlet application contexts will automatically register this with their
* underlying bean factory. Applications do not use this directly.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.context.PortletContextAware
* @see org.springframework.web.portlet.context.XmlPortletApplicationContext#postProcessBeanFactory
*/
public class PortletContextAwareProcessor implements BeanPostProcessor {
private PortletContext portletContext;
private PortletConfig portletConfig;
/**
* Create a new PortletContextAwareProcessor for the given context.
*/
public PortletContextAwareProcessor(PortletContext portletContext) {
this(portletContext, null);
}
/**
* Create a new PortletContextAwareProcessor for the given config.
*/
public PortletContextAwareProcessor(PortletConfig portletConfig) {
this(null, portletConfig);
}
/**
* Create a new PortletContextAwareProcessor for the given context and config.
*/
public PortletContextAwareProcessor(PortletContext portletContext, PortletConfig portletConfig) {
this.portletContext = portletContext;
this.portletConfig = portletConfig;
if (portletContext == null && portletConfig != null) {
this.portletContext = portletConfig.getPortletContext();
}
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (this.portletContext != null && bean instanceof PortletContextAware) {
((PortletContextAware) bean).setPortletContext(this.portletContext);
}
if (this.portletConfig != null && bean instanceof PortletConfigAware) {
((PortletConfigAware) bean).setPortletConfig(this.portletConfig);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.portlet.PortletContext;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.portlet.util.PortletUtils;
/**
* {@link org.springframework.core.io.Resource} implementation for
* {@link javax.portlet.PortletContext} resources, interpreting
* relative paths within the portlet application root directory.
*
* <p>Always supports stream access and URL access, but only allows
* <code>java.io.File</code> access when the portlet application archive
* is expanded.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see javax.portlet.PortletContext#getResourceAsStream
* @see javax.portlet.PortletContext#getRealPath
*/
public class PortletContextResource extends AbstractResource implements ContextResource {
private final PortletContext portletContext;
private final String path;
/**
* Create a new PortletContextResource.
* <p>The Portlet spec requires that resource paths start with a slash,
* even if many containers accept paths without leading slash too.
* Consequently, the given path will be prepended with a slash if it
* doesn't already start with one.
* @param portletContext the PortletContext to load from
* @param path the path of the resource
*/
public PortletContextResource(PortletContext portletContext, String path) {
// check PortletContext
Assert.notNull(portletContext, "Cannot resolve PortletContextResource without PortletContext");
this.portletContext = portletContext;
// check path
Assert.notNull(path, "Path is required");
String pathToUse = StringUtils.cleanPath(path);
if (!pathToUse.startsWith("/")) {
pathToUse = "/" + pathToUse;
}
this.path = pathToUse;
}
/**
* Return the PortletContext for this resource.
*/
public final PortletContext getPortletContext() {
return this.portletContext;
}
/**
* Return the path for this resource.
*/
public final String getPath() {
return this.path;
}
/**
* This implementation checks <code>PortletContext.getResource</code>.
* @see javax.portlet.PortletContext#getResource(String)
*/
public boolean exists() {
try {
URL url = this.portletContext.getResource(this.path);
return (url != null);
}
catch (MalformedURLException ex) {
return false;
}
}
/**
* This implementation delegates to <code>PortletContext.getResourceAsStream</code>,
* but throws a FileNotFoundException if not found.
* @see javax.portlet.PortletContext#getResourceAsStream(String)
*/
public InputStream getInputStream() throws IOException {
InputStream is = this.portletContext.getResourceAsStream(this.path);
if (is == null) {
throw new FileNotFoundException("Could not open " + getDescription());
}
return is;
}
/**
* This implementation delegates to <code>PortletContext.getResource</code>,
* but throws a FileNotFoundException if no resource found.
* @see javax.portlet.PortletContext#getResource(String)
*/
public URL getURL() throws IOException {
URL url = this.portletContext.getResource(this.path);
if (url == null) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* This implementation delegates to <code>PortletContext.getRealPath</code>,
* but throws a FileNotFoundException if not found or not resolvable.
* @see javax.portlet.PortletContext#getRealPath(String)
*/
public File getFile() throws IOException {
String realPath = PortletUtils.getRealPath(this.portletContext, this.path);
return new File(realPath);
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new PortletContextResource(this.portletContext, pathToUse);
}
public String getFilename() {
return StringUtils.getFilename(this.path);
}
public String getDescription() {
return "PortletContext resource [" + this.path + "]";
}
public String getPathWithinContext() {
return this.path;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof PortletContextResource) {
PortletContextResource otherRes = (PortletContextResource) obj;
return (this.portletContext.equals(otherRes.portletContext) && this.path.equals(otherRes.path));
}
return false;
}
public int hashCode() {
return this.path.hashCode();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
/**
* ResourceLoader implementation that resolves paths as PortletContext
* resources, for use outside a Portlet ApplicationContext (for example,
* in a GenericPortletBean subclass).
*
* <p>Within a WebApplicationContext, resource paths are automatically
* resolved as PortletContext resources by the context implementation.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #getResourceByPath
* @see PortletContextResource
* @see org.springframework.web.portlet.GenericPortletBean
*/
public class PortletContextResourceLoader extends DefaultResourceLoader {
private final PortletContext portletContext;
/**
* Create a new PortletContextResourceLoader.
* @param portletContext the PortletContext to load resources with
*/
public PortletContextResourceLoader(PortletContext portletContext) {
this.portletContext = portletContext;
}
/**
* This implementation supports file paths beneath the root of the web application.
* @see PortletContextResource
*/
protected Resource getResourceByPath(String path) {
return new PortletContextResource(this.portletContext, path);
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.portlet.PortletContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.StringUtils;
/**
* PortletContext-aware subclass of {@link PathMatchingResourcePatternResolver},
* able to find matching resources below the web application root directory
* via Portlet API's <code>PortletContext.getResourcePaths</code>.
* Falls back to the superclass' file system checking for other resources.
*
* <p>The advantage of using <code>PortletContext.getResourcePaths</code> to
* find matching files is that it will work in a WAR file which has not been
* expanded too.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public class PortletContextResourcePatternResolver extends PathMatchingResourcePatternResolver {
/**
* Create a new PortletContextResourcePatternResolver.
* @param portletContext the PortletContext to load resources with
* @see PortletContextResourceLoader#PortletContextResourceLoader(javax.portlet.PortletContext)
*/
public PortletContextResourcePatternResolver(PortletContext portletContext) {
super(new PortletContextResourceLoader(portletContext));
}
/**
* Create a new PortletContextResourcePatternResolver.
* @param resourceLoader the ResourceLoader to load root directories and
* actual resources with
*/
public PortletContextResourcePatternResolver(ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* Overridden version which checks for PortletContextResource
* and uses <code>PortletContext.getResourcePaths</code> to find
* matching resources below the web application root directory.
* In case of other resources, delegates to the superclass version.
* @see #doRetrieveMatchingPortletContextResources
* @see org.springframework.web.portlet.context.PortletContextResource
* @see javax.portlet.PortletContext#getResourcePaths
*/
protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException {
if (rootDirResource instanceof PortletContextResource) {
PortletContextResource pcResource = (PortletContextResource) rootDirResource;
PortletContext pc = pcResource.getPortletContext();
String fullPattern = pcResource.getPath() + subPattern;
Set result = new HashSet();
doRetrieveMatchingPortletContextResources(pc, fullPattern, pcResource.getPath(), result);
return result;
}
return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
}
/**
* Recursively retrieve PortletContextResources that match the given pattern,
* adding them to the given result set.
* @param portletContext the PortletContext to work on
* @param fullPattern the pattern to match against,
* with preprended root directory path
* @param dir the current directory
* @param result the Set of matching Resources to add to
* @throws IOException if directory contents could not be retrieved
* @see org.springframework.web.portlet.context.PortletContextResource
* @see javax.portlet.PortletContext#getResourcePaths
*/
protected void doRetrieveMatchingPortletContextResources(
PortletContext portletContext, String fullPattern, String dir, Set result) throws IOException {
Set candidates = portletContext.getResourcePaths(dir);
if (candidates != null) {
boolean dirDepthNotFixed = (fullPattern.indexOf("**") != -1);
for (Iterator it = candidates.iterator(); it.hasNext();) {
String currPath = (String) it.next();
if (currPath.endsWith("/") &&
(dirDepthNotFixed ||
StringUtils.countOccurrencesOf(currPath, "/") <= StringUtils.countOccurrencesOf(fullPattern, "/"))) {
doRetrieveMatchingPortletContextResources(portletContext, fullPattern, currPath, result);
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(new PortletContextResource(portletContext, currPath));
}
}
}
}
}

View File

@ -0,0 +1,287 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.AbstractRequestAttributes;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.portlet.util.PortletUtils;
/**
* Portlet-based implementation of the
* {@link org.springframework.web.context.request.RequestAttributes} interface.
*
* <p>Accesses objects from portlet request and portlet session scope,
* with a distinction between "session" (the PortletSession's "portlet scope")
* and "global session" (the PortletSession's "application scope").
*
* @author Juergen Hoeller
* @since 2.0
* @see javax.portlet.PortletRequest#getAttribute
* @see javax.portlet.PortletSession#getAttribute
* @see javax.portlet.PortletSession#PORTLET_SCOPE
* @see javax.portlet.PortletSession#APPLICATION_SCOPE
* @see RequestAttributes#SCOPE_SESSION
* @see RequestAttributes#SCOPE_GLOBAL_SESSION
*/
public class PortletRequestAttributes extends AbstractRequestAttributes {
/**
* We'll create a lot of these objects, so we don't want a new logger every time.
*/
private static final Log logger = LogFactory.getLog(PortletRequestAttributes.class);
private final PortletRequest request;
private volatile PortletSession session;
private final Map sessionAttributesToUpdate = new HashMap();
private final Map globalSessionAttributesToUpdate = new HashMap();
/**
* Create a new PortletRequestAttributes instance for the given request.
* @param request current portlet request
*/
public PortletRequestAttributes(PortletRequest request) {
Assert.notNull(request, "Request must not be null");
this.request = request;
}
/**
* Exposes the native {@link PortletRequest} that we're wrapping.
*/
public final PortletRequest getRequest() {
return this.request;
}
/**
* Exposes the {@link PortletSession} that we're wrapping.
* @param allowCreate whether to allow creation of a new session if none exists yet
*/
protected final PortletSession getSession(boolean allowCreate) {
if (isRequestActive()) {
return this.request.getPortletSession(allowCreate);
}
else {
// Access through stored session reference, if any...
if (this.session == null && allowCreate) {
throw new IllegalStateException(
"No session found and request already completed - cannot create new session!");
}
return this.session;
}
}
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
PortletSession session = getSession(false);
if (session != null) {
if (scope == SCOPE_GLOBAL_SESSION) {
Object value = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
if (value != null) {
synchronized (this.globalSessionAttributesToUpdate) {
this.globalSessionAttributesToUpdate.put(name, value);
}
}
return value;
}
else {
Object value = session.getAttribute(name);
if (value != null) {
synchronized (this.sessionAttributesToUpdate) {
this.sessionAttributesToUpdate.put(name, value);
}
}
return value;
}
}
else {
return null;
}
}
}
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
PortletSession session = getSession(true);
if (scope == SCOPE_GLOBAL_SESSION) {
session.setAttribute(name, value, PortletSession.APPLICATION_SCOPE);
synchronized (this.globalSessionAttributesToUpdate) {
this.globalSessionAttributesToUpdate.remove(name);
}
}
else {
session.setAttribute(name, value);
synchronized (this.sessionAttributesToUpdate) {
this.sessionAttributesToUpdate.remove(name);
}
}
}
}
public void removeAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (isRequestActive()) {
this.request.removeAttribute(name);
removeRequestDestructionCallback(name);
}
}
else {
PortletSession session = getSession(false);
if (session != null) {
if (scope == SCOPE_GLOBAL_SESSION) {
session.removeAttribute(name, PortletSession.APPLICATION_SCOPE);
synchronized (this.globalSessionAttributesToUpdate) {
this.globalSessionAttributesToUpdate.remove(name);
}
}
else {
session.removeAttribute(name);
synchronized (this.sessionAttributesToUpdate) {
this.sessionAttributesToUpdate.remove(name);
}
}
}
}
}
public String[] getAttributeNames(int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attributes - request is not active anymore!");
}
return StringUtils.toStringArray(this.request.getAttributeNames());
}
else {
PortletSession session = getSession(false);
if (session != null) {
if (scope == SCOPE_GLOBAL_SESSION) {
return StringUtils.toStringArray(session.getAttributeNames(PortletSession.APPLICATION_SCOPE));
}
else {
return StringUtils.toStringArray(session.getAttributeNames());
}
}
else {
return new String[0];
}
}
}
public void registerDestructionCallback(String name, Runnable callback, int scope) {
if (scope == SCOPE_REQUEST) {
registerRequestDestructionCallback(name, callback);
}
else {
registerSessionDestructionCallback(name, callback);
}
}
public String getSessionId() {
return getSession(true).getId();
}
public Object getSessionMutex() {
return PortletUtils.getSessionMutex(getSession(true));
}
/**
* Update all accessed session attributes through <code>session.setAttribute</code>
* calls, explicitly indicating to the container that they might have been modified.
*/
protected void updateAccessedSessionAttributes() {
this.session = this.request.getPortletSession(false);
synchronized (this.sessionAttributesToUpdate) {
if (this.session != null) {
for (Iterator it = this.sessionAttributesToUpdate.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
Object newValue = entry.getValue();
Object oldValue = this.session.getAttribute(name);
if (oldValue == newValue) {
this.session.setAttribute(name, newValue);
}
}
}
this.sessionAttributesToUpdate.clear();
}
synchronized (this.globalSessionAttributesToUpdate) {
if (this.session != null) {
for (Iterator it = this.globalSessionAttributesToUpdate.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
Object newValue = entry.getValue();
Object oldValue = this.session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
if (oldValue == newValue) {
this.session.setAttribute(name, newValue, PortletSession.APPLICATION_SCOPE);
}
}
}
this.globalSessionAttributesToUpdate.clear();
}
}
/**
* Register the given callback as to be executed after session termination.
* @param name the name of the attribute to register the callback for
* @param callback the callback to be executed for destruction
*/
private void registerSessionDestructionCallback(String name, Runnable callback) {
if (logger.isWarnEnabled()) {
logger.warn("Could not register destruction callback [" + callback + "] for attribute '" + name +
"' for session scope because Portlet API 1.0 does not support session attribute callbacks");
}
}
public String toString() {
return this.request.toString();
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import org.springframework.web.context.support.RequestHandledEvent;
/**
* Portlet-specific subclass of RequestHandledEvent,
* adding portlet-specific context information.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.FrameworkPortlet
* @see org.springframework.context.ApplicationContext#publishEvent
*/
public class PortletRequestHandledEvent extends RequestHandledEvent {
/** Name of the portlet that handled the request */
private final String portletName;
/** PortletMode of the request */
private final String portletMode;
/** Type of Portlet Request */
private final String requestType;
/**
* Create a new PortletRequestHandledEvent.
* @param source the component that published the event
* @param portletName the name of the portlet that handled the request
* @param portletMode the PortletMode of the request (usually 'view', 'edit', or 'help')
* @param requestType the type of Portlet request ('action' or 'render')
* @param sessionId the id of the HTTP session, if any
* @param userName the name of the user that was associated with the
* request, if any (usually the UserPrincipal)
* @param processingTimeMillis the processing time of the request in milliseconds
*/
public PortletRequestHandledEvent(Object source, String portletName,
String portletMode, String requestType, String sessionId,
String userName, long processingTimeMillis) {
super(source, sessionId, userName, processingTimeMillis);
this.portletName = portletName;
this.portletMode = portletMode;
this.requestType = requestType;
}
/**
* Create a new PortletRequestHandledEvent.
* @param source the component that published the event
* @param portletName the name of the portlet that handled the request
* @param portletMode the PortletMode of the request (usually 'view', 'edit', or 'help')
* @param requestType the type of Portlet request ('action' or 'render')
* @param sessionId the id of the HTTP session, if any
* @param userName the name of the user that was associated with the
* request, if any (usually the UserPrincipal)
* @param processingTimeMillis the processing time of the request in milliseconds
* @param failureCause the cause of failure, if any
*/
public PortletRequestHandledEvent(Object source, String portletName,
String portletMode, String requestType, String sessionId,
String userName, long processingTimeMillis, Throwable failureCause) {
super(source, sessionId, userName, processingTimeMillis, failureCause);
this.portletName = portletName;
this.portletMode = portletMode;
this.requestType = requestType;
}
/**
* Return the name of the portlet that handled the request.
*/
public String getPortletName() {
return this.portletName;
}
/**
* Return the mode of the portlet request (usually 'view', 'edit', or 'help').
*/
public String getPortletMode() {
return this.portletMode;
}
/**
* Return the the type of Portlet Request ('action' or 'render').
*/
public String getRequestType() {
return this.requestType;
}
public String getShortDescription() {
StringBuffer sb = new StringBuffer();
sb.append("portlet=[").append(this.portletName).append("]; ");
sb.append(super.getShortDescription());
return sb.toString();
}
public String getDescription() {
StringBuffer sb = new StringBuffer();
sb.append("portlet=[").append(this.portletName).append("]; ");
sb.append("mode=[").append(this.portletMode).append("]; ");
sb.append("type=[").append(this.requestType).append("]; ");
sb.append(super.getDescription());
return sb.toString();
}
public String toString() {
return "PortletRequestHandledEvent: " + getDescription();
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.security.Principal;
import java.util.Locale;
import java.util.Map;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.NativeWebRequest;
/**
* {@link org.springframework.web.context.request.WebRequest} adapter
* for a {@link javax.portlet.PortletRequest}.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class PortletWebRequest extends PortletRequestAttributes implements NativeWebRequest {
private PortletResponse response;
/**
* Create a new PortletWebRequest instance for the given request.
* @param request current portlet request
*/
public PortletWebRequest(PortletRequest request) {
super(request);
}
/**
* Create a new PortletWebRequest instance for the given request/response pair.
* @param request current portlet request
* @param response current portlet response
*/
public PortletWebRequest(PortletRequest request, PortletResponse response) {
super(request);
this.response = response;
}
/**
* Exposes the native {@link PortletResponse} that we're wrapping (if any).
*/
public final PortletResponse getResponse() {
return this.response;
}
public Object getNativeRequest() {
return getRequest();
}
public Object getNativeResponse() {
return getResponse();
}
public String getParameter(String paramName) {
return getRequest().getParameter(paramName);
}
public String[] getParameterValues(String paramName) {
return getRequest().getParameterValues(paramName);
}
public Map getParameterMap() {
return getRequest().getParameterMap();
}
public Locale getLocale() {
return getRequest().getLocale();
}
public String getContextPath() {
return getRequest().getContextPath();
}
public String getRemoteUser() {
return getRequest().getRemoteUser();
}
public Principal getUserPrincipal() {
return getRequest().getUserPrincipal();
}
public boolean isUserInRole(String role) {
return getRequest().isUserInRole(role);
}
public boolean isSecure() {
return getRequest().isSecure();
}
/**
* Last-modified handling not supported for portlet requests:
* As a consequence, this method always returns <code>false</code>.
*/
public boolean checkNotModified(long lastModifiedTimestamp) {
return false;
}
public String getDescription(boolean includeClientInfo) {
PortletRequest request = getRequest();
StringBuffer buffer = new StringBuffer();
buffer.append("context=").append(request.getContextPath());
if (includeClientInfo) {
PortletSession session = request.getPortletSession(false);
if (session != null) {
buffer.append(";session=").append(session.getId());
}
String user = getRequest().getRemoteUser();
if (StringUtils.hasLength(user)) {
buffer.append(";user=").append(user);
}
}
return buffer.toString();
}
public String toString() {
return "PortletWebRequest: " + getDescription(true);
}
}

View File

@ -0,0 +1,163 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.ServletContextAwareProcessor;
/**
* Static Portlet-based {@link org.springframework.context.ApplicationContext}
* implementation for testing. Not intended for use in production applications.
*
* <p>Implements the
* {@link org.springframework.web.portlet.context.ConfigurablePortletApplicationContext}
* interface to allow for direct replacement of an {@link XmlPortletApplicationContext},
* despite not actually supporting external configuration files.
*
* <p>Interprets resource paths as portlet context resources, that is, as paths
* beneath the portlet application root. Absolute paths, for example for files
* outside the portlet app root, can be accessed via "file:" URLs, as implemented
* by {@link org.springframework.core.io.DefaultResourceLoader}.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @since 2.0
*/
public class StaticPortletApplicationContext extends StaticApplicationContext
implements ConfigurablePortletApplicationContext {
private ServletContext servletContext;
private PortletContext portletContext;
private PortletConfig portletConfig;
private String namespace;
public StaticPortletApplicationContext() {
setDisplayName("Root Portlet ApplicationContext");
}
public void setParent(ApplicationContext parent) {
super.setParent(parent);
if (parent instanceof WebApplicationContext) {
this.servletContext = ((WebApplicationContext) parent).getServletContext();
}
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setPortletContext(PortletContext portletContext) {
this.portletContext = portletContext;
}
public PortletContext getPortletContext() {
return this.portletContext;
}
public void setPortletConfig(PortletConfig portletConfig) {
this.portletConfig = portletConfig;
if (portletConfig != null && this.portletContext == null) {
this.portletContext = portletConfig.getPortletContext();
}
}
public PortletConfig getPortletConfig() {
return this.portletConfig;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
if (namespace != null) {
setDisplayName("Portlet ApplicationContext for namespace '" + namespace + "'");
}
}
public String getNamespace() {
return this.namespace;
}
/**
* The {@link StaticPortletApplicationContext} class does not support this method.
* @throws UnsupportedOperationException <b>always</b>
*/
public void setConfigLocation(String configLocation) {
if (configLocation != null) {
throw new UnsupportedOperationException("StaticPortletApplicationContext does not support config locations");
}
}
/**
* The {@link StaticPortletApplicationContext} class does not support this method.
* @throws UnsupportedOperationException <b>always</b>
*/
public void setConfigLocations(String[] configLocations) {
if (configLocations != null) {
throw new UnsupportedOperationException("StaticPortletApplicationContext does not support config locations");
}
}
public String[] getConfigLocations() {
return null;
}
/**
* Register request/session scopes, a {@link PortletContextAwareProcessor}, etc.
*/
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext));
beanFactory.addBeanPostProcessor(new PortletContextAwareProcessor(this.portletContext, this.portletConfig));
beanFactory.ignoreDependencyInterface(PortletContextAware.class);
beanFactory.ignoreDependencyInterface(PortletConfigAware.class);
beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext);
beanFactory.registerResolvableDependency(PortletContext.class, this.portletContext);
beanFactory.registerResolvableDependency(PortletConfig.class, this.portletConfig);
PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory);
}
/**
* This implementation supports file paths beneath the root of the PortletContext.
* @see PortletContextResource
*/
protected Resource getResourceByPath(String path) {
return new PortletContextResource(this.portletContext, path);
}
/**
* This implementation supports pattern matching in unexpanded WARs too.
* @see PortletContextResourcePatternResolver
*/
protected ResourcePatternResolver getResourcePatternResolver() {
return new PortletContextResourcePatternResolver(this);
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.context;
import java.io.IOException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
/**
* Portlet-based {@link org.springframework.web.context.WebApplicationContext}
* implementation which takes its configuration from XML documents, understood
* by an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
* This is essentially the equivalent of
* {@link org.springframework.context.support.AbstractXmlApplicationContext}
* for a portlet environment.
*
* <p>By default, the configuration will be taken from "/WEB-INF/applicationContext.xml"
* for the root context, and "/WEB-INF/test-portlet.xml" for a context with the namespace
* "test-portlet" (like for a DispatcherPortlet instance with the portlet-name "test").
*
* <p>The config location defaults can be overridden via the "contextConfigLocation"
* portlet init-param of {@link org.springframework.web.portlet.FrameworkPortlet}.
* Config locations can either denote concrete files like "/WEB-INF/context.xml"
* or Ant-style patterns like "/WEB-INF/*-context.xml" (see
* {@link org.springframework.util.PathMatcher} javadoc for pattern details).
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files. This can be leveraged to
* deliberately override certain bean definitions via an extra XML file.
*
* <p><b>For a Portlet-based context that reads in a different bean definition format,
* create an analogous subclass of {@link AbstractRefreshablePortletApplicationContext}.</b>
* Such a context implementation can be specified as "contextClass" init-param
* for a FrameworkPortlet instance.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #setNamespace
* @see #setConfigLocations
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see org.springframework.web.portlet.FrameworkPortlet#initPortletApplicationContext
*/
public class XmlPortletApplicationContext extends AbstractRefreshablePortletApplicationContext {
/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** Default prefix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** Default suffix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
/**
* Initialize the bean definition reader used for loading the bean
* definitions of this context. Default implementation is empty.
* <p>Can be overridden in subclasses, e.g. for turning off XML validation
* or using a different XmlBeanDefinitionParser implementation.
* @param beanDefinitionReader the bean definition reader used by this context
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setValidationMode
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
* <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method;
* therefore this method is just supposed to load and/or register bean definitions.
* <p>Delegates to a ResourcePatternResolver for resolving location patterns
* into Resource instances.
* @throws org.springframework.beans.BeansException in case of bean registration errors
* @throws java.io.IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (int i = 0; i < configLocations.length; i++) {
reader.loadBeanDefinitions(configLocations[i]);
}
}
}
/**
* The default location for the root context is "/WEB-INF/applicationContext.xml",
* and "/WEB-INF/test-portlet.xml" for a context with the namespace "test-portlet"
* (like for a DispatcherPortlet instance with the portlet-name "test").
*/
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Support for Spring's application context concept in a portlet environment,
including ApplicationContext implementations and various utility classes.
</body>
</html>

View File

@ -0,0 +1,261 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.Ordered;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.portlet.HandlerExecutionChain;
import org.springframework.web.portlet.HandlerInterceptor;
import org.springframework.web.portlet.HandlerMapping;
/**
* Abstract base class for {@link org.springframework.web.portlet.HandlerMapping}
* implementations. Supports ordering, a default handler, and handler interceptors.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #getHandlerInternal
* @see #setDefaultHandler
* @see #setInterceptors
* @see org.springframework.web.portlet.HandlerInterceptor
*/
public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
implements HandlerMapping, Ordered {
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Object defaultHandler;
private final List interceptors = new ArrayList();
private boolean applyWebRequestInterceptorsToRenderPhaseOnly = true;
private HandlerInterceptor[] adaptedInterceptors;
/**
* Specify the order value for this HandlerMapping bean.
* <p>Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public final void setOrder(int order) {
this.order = order;
}
public final int getOrder() {
return this.order;
}
/**
* Set the default handler for this handler mapping.
* This handler will be returned if no specific mapping was found.
* <p>Default is <code>null</code>, indicating no default handler.
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
}
/**
* Return the default handler for this handler mapping,
* or <code>null</code> if none.
*/
public Object getDefaultHandler() {
return this.defaultHandler;
}
/**
* Set the interceptors to apply for all handlers mapped by this handler mapping.
* <p>Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
* Each given WebRequestInterceptor will be wrapped in a WebRequestHandlerInterceptorAdapter.
* @param interceptors array of handler interceptors, or <code>null</code> if none
* @see #adaptInterceptor
* @see org.springframework.web.portlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
*/
public void setInterceptors(Object[] interceptors) {
this.interceptors.addAll(Arrays.asList(interceptors));
}
/**
* Specify whether to apply WebRequestInterceptors to the Portlet render phase
* only ("true", or whether to apply them to the Portlet action phase as well
* ("false").
* <p>Default is "true", since WebRequestInterceptors are usually built for
* MVC-style handler execution plus rendering process (which is, for example,
* the primary target scenario for "Open Session in View" interceptors,
* offering lazy loading of persistent objects during view rendering).
* Set this to "false" to have WebRequestInterceptors apply to the action
* phase as well (for example, in case of an "Open Session in View" interceptor,
* to allow for lazy loading outside of a transaction during the action phase).
* @see #setInterceptors
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter#WebRequestHandlerInterceptorAdapter(WebRequestInterceptor, boolean)
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
*/
public void setApplyWebRequestInterceptorsToRenderPhaseOnly(boolean applyWebRequestInterceptorsToRenderPhaseOnly) {
this.applyWebRequestInterceptorsToRenderPhaseOnly = applyWebRequestInterceptorsToRenderPhaseOnly;
}
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
initInterceptors();
}
/**
* Extension hook that subclasses can override to register additional interceptors,
* given the configured interceptors (see {@link #setInterceptors}).
* <p>Will be invoked before {@link #initInterceptors()} adapts the specified
* interceptors into {@link HandlerInterceptor} instances.
* <p>The default implementation is empty.
* @param interceptors the configured interceptor List (never <code>null</code>),
* allowing to add further interceptors before as well as after the existing
* interceptors
*/
protected void extendInterceptors(List interceptors) {
}
/**
* Initialize the specified interceptors, adapting them where necessary.
* @see #setInterceptors
* @see #adaptInterceptor
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
this.adaptedInterceptors = new HandlerInterceptor[this.interceptors.size()];
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors[i] = adaptInterceptor(interceptor);
}
}
}
/**
* Adapt the given interceptor object to the HandlerInterceptor interface.
* <p>Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
* Each given WebRequestInterceptor will be wrapped in a WebRequestHandlerInterceptorAdapter.
* Can be overridden in subclasses.
* @param interceptor the specified interceptor object
* @return the interceptor wrapped as HandlerInterceptor
* @see #setApplyWebRequestInterceptorsToRenderPhaseOnly
* @see org.springframework.web.portlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter
*/
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter(
(WebRequestInterceptor) interceptor, this.applyWebRequestInterceptorsToRenderPhaseOnly);
}
else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
}
/**
* Return the adapted interceptors as HandlerInterceptor array.
* @return the array of HandlerInterceptors, or <code>null</code> if none
*/
protected final HandlerInterceptor[] getAdaptedInterceptors() {
return this.adaptedInterceptors;
}
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current portlet request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
public final HandlerExecutionChain getHandler(PortletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
/**
* Look up a handler for the given request, returning <code>null</code> if no
* specific one is found. This method is called by {@link #getHandler};
* a <code>null</code> return value will lead to the default handler, if one is set.
* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
* combining a handler object with dynamically determined interceptors.
* Statically specified interceptors will get merged into such an existing chain.
* @param request current portlet request
* @return the corresponding handler instance, or <code>null</code> if none found
* @throws Exception if there is an internal error
* @see #getHandler
*/
protected abstract Object getHandlerInternal(PortletRequest request) throws Exception;
/**
* Build a HandlerExecutionChain for the given handler, including applicable interceptors.
* <p>The default implementation simply builds a standard HandlerExecutionChain with
* the given handler and this handler mapping's common interceptors. Subclasses may
* override this in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a pre-built
* HandlerExecutionChain. This method should handle those two cases explicitly,
* either building a new HandlerExecutionChain or extending the existing chain.
* <p>For simply adding an interceptor, consider calling <code>super.getHandlerExecutionChain</code>
* and invoking {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never <code>null</code>)
* @param request current HTTP request
* @return the HandlerExecutionChain (never <code>null</code>)
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, PortletRequest request) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain chain = (HandlerExecutionChain) handler;
chain.addInterceptors(getAdaptedInterceptors());
return chain;
}
else {
return new HandlerExecutionChain(handler, getAdaptedInterceptors());
}
}
}

View File

@ -0,0 +1,190 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link org.springframework.web.portlet.HandlerMapping}
* implementations that rely on a map which caches handler objects per lookup key.
* Supports arbitrary lookup keys, and automatically resolves handler bean names
* into handler bean instances.
*
* @author Juergen Hoeller
* @since 2.0
* @see #getLookupKey(javax.portlet.PortletRequest)
* @see #registerHandler(Object, Object)
*/
public abstract class AbstractMapBasedHandlerMapping extends AbstractHandlerMapping {
private boolean lazyInitHandlers = false;
private final Map handlerMap = new HashMap();
/**
* Set whether to lazily initialize handlers. Only applicable to
* singleton handlers, as prototypes are always lazily initialized.
* Default is false, as eager initialization allows for more efficiency
* through referencing the handler objects directly.
* <p>If you want to allow your handlers to be lazily initialized,
* make them "lazy-init" and set this flag to true. Just making them
* "lazy-init" will not work, as they are initialized through the
* references from the handler mapping in this case.
*/
public void setLazyInitHandlers(boolean lazyInitHandlers) {
this.lazyInitHandlers = lazyInitHandlers;
}
/**
* Determines a handler for the computed lookup key for the given request.
* @see #getLookupKey
*/
protected Object getHandlerInternal(PortletRequest request) throws Exception {
Object lookupKey = getLookupKey(request);
Object handler = this.handlerMap.get(lookupKey);
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Key [" + lookupKey + "] -> handler [" + handler + "]");
}
if (handler instanceof Map) {
Map predicateMap = (Map) handler;
List predicates = new LinkedList(predicateMap.keySet());
Collections.sort(predicates);
for (Iterator it = predicates.iterator(); it.hasNext();) {
PortletRequestMappingPredicate predicate = (PortletRequestMappingPredicate) it.next();
if (predicate.match(request)) {
return predicateMap.get(predicate);
}
}
return null;
}
return handler;
}
/**
* Build a lookup key for the given request.
* @param request current portlet request
* @return the lookup key (never <code>null</code>)
* @throws Exception if key computation failed
*/
protected abstract Object getLookupKey(PortletRequest request) throws Exception;
/**
* Register all handlers specified in the Portlet mode map for the corresponding modes.
* @param handlerMap Map with lookup keys as keys and handler beans or bean names as values
* @throws BeansException if the handler couldn't be registered
*/
protected void registerHandlers(Map handlerMap) throws BeansException {
Assert.notNull(handlerMap, "Handler Map must not be null");
for (Iterator it = handlerMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
registerHandler(entry.getKey(), entry.getValue());
}
}
/**
* Register the given handler instance for the given parameter value.
* @param lookupKey the key to map the handler onto
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(Object lookupKey, Object handler) throws BeansException, IllegalStateException {
registerHandler(lookupKey, handler, null);
}
/**
* Register the given handler instance for the given parameter value.
* @param lookupKey the key to map the handler onto
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @param predicate a predicate object for this handler (may be <code>null</code>),
* determining a match with the primary lookup key
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(Object lookupKey, Object handler, PortletRequestMappingPredicate predicate)
throws BeansException, IllegalStateException {
Assert.notNull(lookupKey, "Lookup key must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
// Check for duplicate mapping.
Object mappedHandler = this.handlerMap.get(lookupKey);
if (mappedHandler != null && !(mappedHandler instanceof Map)) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException("Cannot map handler [" + handler + "] to key [" + lookupKey +
"]: There's already handler [" + mappedHandler + "] mapped.");
}
}
else {
if (predicate != null) {
// Add the handler to the predicate map.
Map predicateMap = (Map) mappedHandler;
if (predicateMap == null) {
predicateMap = new LinkedHashMap();
this.handlerMap.put(lookupKey, predicateMap);
}
predicateMap.put(predicate, resolvedHandler);
}
else {
// Add the single handler to the map.
this.handlerMap.put(lookupKey, resolvedHandler);
}
if (logger.isDebugEnabled()) {
logger.debug("Mapped key [" + lookupKey + "] onto handler [" + resolvedHandler + "]");
}
}
}
/**
* Predicate interface for determining a match with a given request.
*/
protected interface PortletRequestMappingPredicate extends Comparable {
/**
* Determine whether the given request matches this predicate.
* @param request current portlet request
*/
boolean match(PortletRequest request);
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletException;
import org.springframework.web.portlet.HandlerInterceptor;
import org.springframework.web.portlet.ModelAndView;
/**
* Abstract adapter class for the HandlerInterceptor interface,
* for simplified implementation of pre-only/post-only interceptors.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
/**
* This implementation delegates to <code>preHandle</code>.
* @see #preHandle
*/
public boolean preHandleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception {
return preHandle(request, response, handler);
}
/**
* This implementation delegates to <code>afterCompletion</code>.
* @see #afterCompletion
*/
public void afterActionCompletion(
ActionRequest request, ActionResponse response, Object handler, Exception ex) throws Exception {
afterCompletion(request, response, handler, ex);
}
/**
* This implementation delegates to <code>preHandle</code>.
* @see #preHandle
*/
public boolean preHandleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception {
return preHandle(request, response, handler);
}
/**
* This implementation is empty.
*/
public void postHandleRender(
RenderRequest request, RenderResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* This implementation delegates to <code>afterCompletion</code>.
* @see #afterCompletion
*/
public void afterRenderCompletion(
RenderRequest request, RenderResponse response, Object handler, Exception ex) throws Exception {
afterCompletion(request, response, handler, ex);
}
/**
* Default callback that both <code>preHandleRender</code>
* and <code>preHandleAction</code> delegate to.
* <p>This implementation always returns <code>true</code>.
* @see #preHandleRender
* @see #preHandleAction
*/
protected boolean preHandle(PortletRequest request, PortletResponse response, Object handler)
throws Exception {
return true;
}
/**
* Default callback that both <code>preHandleRender</code>
* and <code>preHandleAction</code> delegate to.
* <p>This implementation is empty.
* @see #afterRenderCompletion
* @see #afterActionCompletion
*/
protected void afterCompletion(
PortletRequest request, PortletResponse response, Object handler, Exception ex) throws Exception {
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.Map;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Implementation of the {@link org.springframework.web.portlet.HandlerMapping}
* to map from a request parameter to request handler beans.
*
* <p>The default name of the parameter is "action", but can be changed using
* {@link #setParameterName setParameterName()}.
*
* <p>The bean configuration for this mapping will look somthing like this:
*
* <pre class="code">
* &lt;bean id="parameterHandlerMapping" class="org.springframework.web.portlet.handler.ParameterHandlerMapping"&gt;
* &lt;property name="parameterMap"&gt;
* &lt;map&gt;
* &lt;entry key="add"&gt;&lt;ref bean="addItemHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="edit"&gt;&lt;ref bean="editItemHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="delete"&gt;&lt;ref bean="deleteItemHandler"/&gt;&lt;/entry&gt;
* &lt;/map&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* Thanks to Rainer Schmitz for suggesting this mapping strategy!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see ParameterMappingInterceptor
*/
public class ParameterHandlerMapping extends AbstractMapBasedHandlerMapping {
/**
* Default request parameter name to use for mapping to handlers: "action".
*/
public final static String DEFAULT_PARAMETER_NAME = "action";
private String parameterName = DEFAULT_PARAMETER_NAME;
private Map parameterMap;
/**
* Set the name of the parameter used for mapping to handlers.
* <p>Default is "action".
*/
public void setParameterName(String parameterName) {
Assert.hasText(parameterName, "'parameterName' must not be empty");
this.parameterName = parameterName;
}
/**
* Set a Map with parameters as keys and handler beans or bean names as values.
* Convenient for population with bean references.
* @param parameterMap map with parameters as keys and beans as values
*/
public void setParameterMap(Map parameterMap) {
this.parameterMap = parameterMap;
}
/**
* Calls the <code>registerHandlers</code> method in addition
* to the superclass's initialization.
* @see #registerHandlers
*/
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.parameterMap);
}
/**
* Register all handlers specified in the Portlet mode map for the corresponding modes.
* @param parameterMap Map with parameter names as keys and handler beans or bean names as values
* @throws BeansException if the handler couldn't be registered
*/
protected void registerHandlers(Map parameterMap) throws BeansException {
if (CollectionUtils.isEmpty(parameterMap)) {
logger.warn("'parameterMap' is empty on ParameterHandlerMapping");
}
else {
super.registerHandlers(parameterMap);
}
}
/**
* Uses the value of the specified parameter as lookup key.
* @see #setParameterName
*/
protected Object getLookupKey(PortletRequest request) throws Exception {
return request.getParameter(this.parameterName);
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
/**
* Interceptor to forward a request parameter from the <code>ActionRequest</code> to the
* <code>RenderRequest</code>.
*
* <p>This can be useful when using {@link ParameterHandlerMapping ParameterHandlerMapping}
* or {@link PortletModeParameterHandlerMapping PortletModeParameterHandlerMapping}.
* It will ensure that the parameter that was used to map the <code>ActionRequest</code>
* to a handler will be forwarded to the <code>RenderRequest</code> so that it will also be
* mapped the same way.
*
* <p>When using this Interceptor, you can still change the value of the mapping parameter
* in your handler in order to change where the render request will get mapped.
*
* <p>Be aware that this Interceptor does call <code>ActionResponse.setRenderParameter</code>,
* which means that you will not be able to call <code>ActionResponse.sendRedirect</code> in
* your handler. If you may need to issue a redirect, then you should avoid this Interceptor
* and either write a different one that does this in a different way, or manually forward
* the parameter from within your handler(s).
*
* <p>Thanks to Rainer Schmitz for suggesting this mapping strategy!
*
* @author John A. Lewis
* @since 2.0
* @see ParameterHandlerMapping
* @see PortletModeParameterHandlerMapping
*/
public class ParameterMappingInterceptor extends HandlerInterceptorAdapter {
/** Request parameter name to use for mapping to handlers */
public final static String DEFAULT_PARAMETER_NAME = "action";
private String parameterName = DEFAULT_PARAMETER_NAME;
/**
* Set the name of the parameter used for mapping.
*/
public void setParameterName(String parameterName) {
this.parameterName = (parameterName != null ? parameterName : DEFAULT_PARAMETER_NAME);
}
/**
* If request is an {@link javax.portlet.ActionRequest ActionRequest},
* get handler mapping parameter and add it to the ActionResponse.
*/
public boolean preHandleAction(ActionRequest request, ActionResponse response, Object handler) {
String mappingParameter = request.getParameter(this.parameterName);
if (mappingParameter != null) {
response.setRenderParameter(parameterName, mappingParameter);
}
return true;
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.context.PortletApplicationObjectSupport;
/**
* Convenient superclass for any kind of web content generator,
* like {@link org.springframework.web.portlet.mvc.AbstractController}.
* Can also be used for custom handlers that have their own
* {@link org.springframework.web.portlet.HandlerAdapter}.
*
* <p>Supports portlet cache control options.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #setCacheSeconds
* @see #setRequireSession
*/
public abstract class PortletContentGenerator extends PortletApplicationObjectSupport {
private boolean requireSession = false;
private int cacheSeconds = -1;
/**
* Set whether a session should be required to handle requests.
*/
public final void setRequireSession(boolean requireSession) {
this.requireSession = requireSession;
}
/**
* Return whether a session is required to handle requests.
*/
public final boolean isRequireSession() {
return this.requireSession;
}
/**
* Cache content for the given number of seconds. Default is -1,
* indicating no override of portlet content caching.
* <p>Only if this is set to 0 (no cache) or a positive value (cache for
* this many seconds) will this class override the portlet settings.
* <p>The cache setting can be overwritten by subclasses, before content is generated.
*/
public final void setCacheSeconds(int seconds) {
this.cacheSeconds = seconds;
}
/**
* Return the number of seconds that content is cached.
*/
public final int getCacheSeconds() {
return this.cacheSeconds;
}
/**
* Check and prepare the given request and response according to the settings
* of this generator. Checks for a required session, and applies the number of
* cache seconds configured for this generator (if it is a render request/response).
* @param request current portlet request
* @param response current portlet response
* @throws PortletException if the request cannot be handled because a check failed
*/
protected final void check(PortletRequest request, PortletResponse response) throws PortletException {
if (this.requireSession) {
if (request.getPortletSession(false) == null) {
throw new PortletSessionRequiredException("Pre-existing session required but none found");
}
}
}
/**
* Check and prepare the given request and response according to the settings
* of this generator. Checks for a required session, and applies the number of
* cache seconds configured for this generator (if it is a render request/response).
* @param request current portlet request
* @param response current portlet response
* @throws PortletException if the request cannot be handled because a check failed
*/
protected final void checkAndPrepare(RenderRequest request, RenderResponse response)
throws PortletException {
checkAndPrepare(request, response, this.cacheSeconds);
}
/**
* Check and prepare the given request and response according to the settings
* of this generator. Checks for a required session, and applies the given
* number of cache seconds (if it is a render request/response).
* @param request current portlet request
* @param response current portlet response
* @param cacheSeconds positive number of seconds into the future that the
* response should be cacheable for, 0 to prevent caching
* @throws PortletException if the request cannot be handled because a check failed
*/
protected final void checkAndPrepare(
RenderRequest request, RenderResponse response, int cacheSeconds)
throws PortletException {
check(request, response);
applyCacheSeconds(response, cacheSeconds);
}
/**
* Prevent the render response from being cached.
*/
protected final void preventCaching(RenderResponse response) {
cacheForSeconds(response, 0);
}
/**
* Set portlet response to allow caching for the given number of seconds.
* @param response current portlet render response
* @param seconds number of seconds into the future that the response
* should be cacheable for
*/
protected final void cacheForSeconds(RenderResponse response, int seconds) {
response.setProperty(RenderResponse.EXPIRATION_CACHE, Integer.toString(seconds));
}
/**
* Apply the given cache seconds to the render response
* @param response current portlet render response
* @param seconds positive number of seconds into the future that the
* response should be cacheable for, 0 to prevent caching
*/
protected final void applyCacheSeconds(RenderResponse response, int seconds) {
if (seconds > 0) {
cacheForSeconds(response, seconds);
}
else if (seconds == 0) {
preventCaching(response);
}
// Leave caching to the portlet configuration otherwise.
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.util.CollectionUtils;
/**
* Implementation of the {@link org.springframework.web.portlet.HandlerMapping}
* interface to map from the current PortletMode to request handler beans.
*
* <p>The bean configuration for this mapping will look something like this:
* <pre>
* &lt;bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping"&gt;
* &lt;property name="portletModeMap"&gt;
* &lt;map&gt;
* &lt;entry key="view"&gt;&lt;ref bean="viewHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="edit"&gt;&lt;ref bean="editHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="help"&gt;&lt;ref bean="helpHandler"/&gt;&lt;/entry&gt;
* &lt;/map&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </pre>
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @since 2.0
*/
public class PortletModeHandlerMapping extends AbstractMapBasedHandlerMapping {
private final Map portletModeMap = new HashMap();
/**
* Set PortletMode to handler bean name mappings from a Properties object.
* @param mappings properties with PortletMode names as keys and bean names as values
*/
public void setMappings(Properties mappings) {
this.portletModeMap.putAll(mappings);
}
/**
* Set a Map with PortletModes as keys and handler beans as values.
* Convenient for population with bean references.
* @param portletModeMap map with PortletMode names as keys and beans or bean names as values
*/
public void setPortletModeMap(Map portletModeMap) {
this.portletModeMap.putAll(portletModeMap);
}
/**
* Calls the <code>registerHandlers</code> method in addition
* to the superclass's initialization.
* @see #registerHandlers
*/
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.portletModeMap);
}
/**
* Register all handlers specified in the Portlet mode map for the corresponding modes.
* @param portletModeMap Map with mode names as keys and handler beans or bean names as values
* @throws BeansException if the handler couldn't be registered
*/
protected void registerHandlers(Map portletModeMap) throws BeansException {
if (CollectionUtils.isEmpty(portletModeMap)) {
logger.warn("Neither 'portletModeMap' nor 'mappings' set on PortletModeHandlerMapping");
}
else {
for (Iterator it = portletModeMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String modeKey = (String) entry.getKey();
PortletMode mode = new PortletMode(modeKey);
Object handler = entry.getValue();
registerHandler(mode, handler);
}
}
}
/**
* Uses the current PortletMode as lookup key.
*/
protected Object getLookupKey(PortletRequest request) throws Exception {
return request.getPortletMode();
}
}

View File

@ -0,0 +1,255 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* Implementation of the {@link org.springframework.web.portlet.HandlerMapping}
* interface to map from the current PortletMode and a request parameter to
* request handler beans. The mapping consists of two levels: first the
* PortletMode and then the parameter value. In order to be mapped,
* both elements must match the mapping definition.
*
* <p>This is a combination of the methods used in {@link PortletModeHandlerMapping PortletModeHandlerMapping}
* and {@link ParameterHandlerMapping ParameterHandlerMapping}. Unlike
* those two classes, this mapping cannot be initialized with properties since it
* requires a two-level map.
*
* <p>The default name of the parameter is "action", but can be changed using
* {@link #setParameterName setParameterName()}.
*
* <p>By default, the same parameter value may not be used in two different portlet
* modes. This is so that if the portal itself changes the portlet mode, the request
* will no longer be valid in the mapping. This behavior can be changed with
* {@link #setAllowDuplicateParameters setAllowDupParameters()}.
*
* <p>The bean configuration for this mapping will look somthing like this:
*
* <pre class="code">
* &lt;bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping"&gt;
* &lt;property name="portletModeParameterMap"&gt;
* &lt;map&gt;
* &lt;entry key="view"&gt; &lt;!-- portlet mode: view --&gt;
* &lt;map&gt;
* &lt;entry key="add"&gt;&lt;ref bean="addItemHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="edit"&gt;&lt;ref bean="editItemHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="delete"&gt;&lt;ref bean="deleteItemHandler"/&gt;&lt;/entry&gt;
* &lt;/map&gt;
* &lt;/entry&gt;
* &lt;entry key="edit"&gt; &lt;!-- portlet mode: edit --&gt;
* &lt;map&gt;
* &lt;entry key="prefs"&gt;&lt;ref bean="preferencesHandler"/&gt;&lt;/entry&gt;
* &lt;entry key="resetPrefs"&gt;&lt;ref bean="resetPreferencesHandler"/&gt;&lt;/entry&gt;
* &lt;/map&gt;
* &lt;/entry&gt;
* &lt;/map&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* <p>This mapping can be chained ahead of a {@link PortletModeHandlerMapping PortletModeHandlerMapping},
* which can then provide defaults for each mode and an overall default as well.
*
* <p>Thanks to Rainer Schmitz and Yujin Kim for suggesting this mapping strategy!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see ParameterMappingInterceptor
*/
public class PortletModeParameterHandlerMapping extends AbstractMapBasedHandlerMapping {
/**
* Default request parameter name to use for mapping to handlers: "action".
*/
public final static String DEFAULT_PARAMETER_NAME = "action";
private String parameterName = DEFAULT_PARAMETER_NAME;
private Map portletModeParameterMap;
private boolean allowDuplicateParameters = false;
private final Set parametersUsed = new HashSet();
/**
* Set the name of the parameter used for mapping to handlers.
* <p>Default is "action".
*/
public void setParameterName(String parameterName) {
Assert.hasText(parameterName, "'parameterName' must not be empty");
this.parameterName = parameterName;
}
/**
* Set a Map with portlet mode names as keys and another Map as values.
* The sub-map has parameter names as keys and handler bean or bean names as values.
* <p>Convenient for population with bean references.
* @param portletModeParameterMap two-level map of portlet modes and parameters to handler beans
*/
public void setPortletModeParameterMap(Map portletModeParameterMap) {
this.portletModeParameterMap = portletModeParameterMap;
}
/**
* Set whether to allow duplicate parameter values across different portlet modes.
* Default is "false".
* <p>Doing this is dangerous because the portlet mode can be changed by the
* portal itself and the only way to see that is a rerender of the portlet.
* If the same parameter value is legal in multiple modes, then a change in
* mode could result in a matched mapping that is not intended and the user
* could end up in a strange place in the application.
*/
public void setAllowDuplicateParameters(boolean allowDuplicateParameters) {
this.allowDuplicateParameters = allowDuplicateParameters;
}
/**
* Calls the <code>registerHandlers</code> method in addition
* to the superclass's initialization.
* @see #registerHandlers
*/
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.portletModeParameterMap);
}
/**
* Register all handlers specified in the Portlet mode map for the corresponding modes.
* @param portletModeParameterMap Map with mode names as keys and parameter Maps as values
* @throws BeansException if the handler couldn't be registered
*/
protected void registerHandlers(Map portletModeParameterMap) throws BeansException {
if (CollectionUtils.isEmpty(portletModeParameterMap)) {
logger.warn("'portletModeParameterMap' not set on PortletModeParameterHandlerMapping");
}
else {
for (Iterator it = portletModeParameterMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String modeKey = (String) entry.getKey();
PortletMode mode = new PortletMode(modeKey);
Object parameterMap = entry.getValue();
if (!(parameterMap instanceof Map)) {
throw new IllegalArgumentException(
"The value for the portlet mode must be a Map of parameter Strings to handler Objects");
}
registerHandler(mode, (Map) parameterMap);
}
}
}
/**
* Register all handlers specified in the given parameter map.
* @param parameterMap Map with parameter names as keys and handler beans or bean names as values
* @throws BeansException if the handler couldn't be registered
*/
protected void registerHandler(PortletMode mode, Map parameterMap) throws BeansException {
for (Iterator it = parameterMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String parameter = (String) entry.getKey();
Object handler = entry.getValue();
registerHandler(mode, parameter, handler);
}
}
/**
* Register the given handler instance for the given PortletMode and parameter value,
* under an appropriate lookup key.
* @param mode the PortletMode for which this mapping is valid
* @param parameter the parameter value to which this handler is mapped
* @param handler the handler instance bean
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
* @see #registerHandler(Object, Object)
*/
protected void registerHandler(PortletMode mode, String parameter, Object handler)
throws BeansException, IllegalStateException {
// Check for duplicate parameter values across all portlet modes.
if (!this.allowDuplicateParameters && this.parametersUsed.contains(parameter)) {
throw new IllegalStateException(
"Duplicate entries for parameter [" + parameter + "] in different Portlet modes");
}
this.parametersUsed.add(parameter);
registerHandler(new LookupKey(mode, parameter), handler);
}
/**
* Returns a lookup key that combines the current PortletMode and the current
* value of the specified parameter.
* @see javax.portlet.PortletRequest#getPortletMode()
* @see #setParameterName
*/
protected Object getLookupKey(PortletRequest request) throws Exception {
PortletMode mode = request.getPortletMode();
String parameter = request.getParameter(this.parameterName);
return new LookupKey(mode, parameter);
}
/**
* Internal class used as lookup key, combining PortletMode and parameter value.
*/
private static class LookupKey {
private final PortletMode mode;
private final String parameter;
public LookupKey(PortletMode portletMode, String parameter) {
this.mode = portletMode;
this.parameter = parameter;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof LookupKey)) {
return false;
}
LookupKey otherKey = (LookupKey) other;
return (this.mode.equals(otherKey.mode) &&
ObjectUtils.nullSafeEquals(this.parameter, otherKey.parameter));
}
public int hashCode() {
return (this.mode.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.parameter));
}
public String toString() {
return "Portlet mode '" + this.mode + "', parameter '" + this.parameter + "'";
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import javax.portlet.PortletException;
/**
* Exception thrown when a portlet content generator requires a pre-existing session.
*
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.handler.PortletContentGenerator
*/
public class PortletSessionRequiredException extends PortletException {
/**
* Create a new PortletSessionRequiredException.
* @param msg the detail message
*/
public PortletSessionRequiredException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,396 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.WindowState;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.portlet.HandlerExceptionResolver;
import org.springframework.web.portlet.ModelAndView;
/**
* {@link org.springframework.web.portlet.HandlerExceptionResolver} implementation
* that allows for mapping exception class names to view names, either for a
* set of given handlers or for all handlers in the DispatcherPortlet.
*
* <p>Error views are analogous to error page JSPs, but can be used with any
* kind of exception including any checked one, with fine-granular mappings for
* specific handlers.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered {
/**
* The default name of the exception attribute: "exception".
*/
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private boolean renderWhenMinimized = false;
private Log warnLogger;
private Properties exceptionMappings;
private String defaultErrorView;
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should map.
* The exception mappings and the default error view will only apply
* to the specified handlers.
* <p>If no handlers set, both the exception mappings and the default error
* view will apply to all handlers. This means that a specified default
* error view will be used as fallback for all exceptions; any further
* HandlerExceptionResolvers in the chain will be ignored in this case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to handlers of the specified type; the specified types may be interfaces
* and superclasses of handlers as well.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set if the resolver should render a view when the portlet is in
* a minimized window. The default is "false".
* @see javax.portlet.RenderRequest#getWindowState()
* @see javax.portlet.WindowState#MINIMIZED
*/
public void setRenderWhenMinimized(boolean renderWhenMinimized) {
this.renderWhenMinimized = renderWhenMinimized;
}
/**
* Set the log category for warn logging. The name will be passed to the
* underlying logger implementation through Commons Logging, getting
* interpreted as log category according to the logger's configuration.
* <p>Default is no warn logging. Specify this setting to activate
* warn logging into a specific category. Alternatively, override
* the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Set the mappings between exception class names and error view names.
* The exception class name can be a substring, with no wildcard support
* at present. A value of "PortletException" would match
* <code>javax.portet.PortletException</code> and subclasses, for example.
* <p><b>NB:</b> Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual exception names such
* as "BaseBusinessException" there's no need to use a FQN.
* <p>Follows the same matching algorithm as RuleBasedTransactionAttribute
* and RollbackRuleAttribute.
* @param mappings exception patterns (can also be fully qualified class names)
* as keys, and error view names as values
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute
*/
public void setExceptionMappings(Properties mappings) {
this.exceptionMappings = mappings;
}
/**
* Set the name of the default error view.
* This view will be returned if no specific mapping was found.
* <p>Default is none.
*/
public void setDefaultErrorView(String defaultErrorView) {
this.defaultErrorView = defaultErrorView;
}
/**
* Set the name of the model attribute as which the exception should
* be exposed. Default is "exception".
* @see #DEFAULT_EXCEPTION_ATTRIBUTE
*/
public void setExceptionAttribute(String exceptionAttribute) {
this.exceptionAttribute = exceptionAttribute;
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler
* matches in case of "mappedHandlers" having been specified), then
* delegates to the {@link #doResolveException} template method.
*/
public ModelAndView resolveException(
RenderRequest request, RenderResponse response, Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the specified mapped handlers
* and handler classes, if any, and alspo checks the window state (according
* to the "renderWhenMinimize" property).
* @param request current portlet request
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(RenderRequest request, Object handler) {
// If the portlet is minimized and we don't want to render then return null.
if (WindowState.MINIMIZED.equals(request.getWindowState()) && !this.renderWhenMinimized) {
return false;
}
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (int i = 0; i < this.mappedHandlerClasses.length; i++) {
if (this.mappedHandlerClasses[i].isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* @param request current portlet request
* @param response current portlet response
* @param handler the executed handler, or null if none chosen at the time of
* the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or null for default processing
*/
protected ModelAndView doResolveException(
RenderRequest request, RenderResponse response, Object handler, Exception ex) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
if (viewName != null) {
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
/**
* Log the given exception at warn level, provided that warn logging has been
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message
* to log. Always passes the full exception to the logger.
* @param ex the exception that got thrown during handler execution
* @param request current portlet request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, RenderRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing
* the given request.
* @param ex the exception that got thrown during handler execution
* @param request current portlet request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, RenderRequest request) {
return "Handler execution resulted in exception";
}
/**
* Determine the view name for the given exception, searching the
* {@link #setExceptionMappings "exceptionMappings"}, using the
* {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* @param ex the exception that got thrown during handler execution
* @param request current portlet request (useful for obtaining metadata)
* @return the resolved view name, or <code>null</code> if none found
*/
protected String determineViewName(Exception ex, RenderRequest request) {
String viewName = null;
// Check for specific exception mappings.
if (this.exceptionMappings != null) {
viewName = findMatchingViewName(this.exceptionMappings, ex);
}
// Return default error view else, if defined.
if (viewName == null && this.defaultErrorView != null) {
if (logger.isDebugEnabled()) {
logger.debug("Resolving to default view '" + this.defaultErrorView +
"' for exception of type [" + ex.getClass().getName() + "]");
}
viewName = this.defaultErrorView;
}
return viewName;
}
/**
* Find a matching view name in the given exception mappings
* @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found
* @see #setExceptionMappings
*/
protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
String viewName = null;
String dominantMapping = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
dominantMapping = exceptionMapping;
viewName = exceptionMappings.getProperty(exceptionMapping);
}
}
if (viewName != null && logger.isDebugEnabled()) {
logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() +
"], based on exception mapping [" + dominantMapping + "]");
}
return viewName;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
* <p>Follows the same algorithm as
* {@link org.springframework.transaction.interceptor.RollbackRuleAttribute}.
*/
protected int getDepth(String exceptionMapping, Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
if (exceptionClass.getName().indexOf(exceptionMapping) != -1) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Return a ModelAndView for the given request, view name and exception.
* Default implementation delegates to <code>getModelAndView(viewName, ex)</code>.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @param request current portlet request (useful for obtaining metadata)
* @return the ModelAndView instance
* @see #getModelAndView(String, Exception)
*/
protected ModelAndView getModelAndView(String viewName, Exception ex, RenderRequest request) {
return getModelAndView(viewName, ex);
}
/**
* Return a ModelAndView for the given view name and exception.
* Default implementation adds the specified exception attribute.
* Can be overridden in subclasses.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance
* @see #setExceptionAttribute
*/
protected ModelAndView getModelAndView(String viewName, Exception ex) {
ModelAndView mv = new ModelAndView(viewName);
if (this.exceptionAttribute != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
}
mv.addObject(this.exceptionAttribute, ex);
}
return mv;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.Portlet;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.HandlerAdapter;
import org.springframework.web.portlet.ModelAndView;
/**
* Adapter to use the Portlet interface with the generic DispatcherPortlet.
* Calls the Portlet's <code>render</code> and <code>processAction</code>
* methods to handle a request.
*
* <p>This adapter is not activated by default; it needs to be defined as a
* bean in the DispatcherPortlet context. It will automatically apply to
* mapped handler beans that implement the Portlet interface then.
*
* <p>Note that Portlet instances defined as bean will not receive initialization
* and destruction callbacks, unless a special post-processor such as
* SimplePortletPostProcessor is defined in the DispatcherPortlet context.
*
* <p><b>Alternatively, consider wrapping a Portlet with Spring's
* PortletWrappingController.</b> This is particularly appropriate for
* existing Portlet classes, allowing to specify Portlet initialization
* parameters, etc.
*
* @author John A. Lewis
* @since 2.0
* @see javax.portlet.Portlet
* @see SimplePortletPostProcessor
* @see org.springframework.web.portlet.mvc.PortletWrappingController
*/
public class SimplePortletHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Portlet);
}
public void handleAction(ActionRequest request, ActionResponse response, Object handler)
throws Exception {
((Portlet) handler).processAction(request, response);
}
public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler)
throws Exception {
((Portlet) handler).render(request, response);
return null;
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.portlet.Portlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.web.portlet.context.PortletConfigAware;
import org.springframework.web.portlet.context.PortletContextAware;
/**
* Bean post-processor that applies initialization and destruction callbacks
* to beans that implement the Portlet interface.
*
* <p>After initialization of the bean instance, the Portlet <code>init</code>
* method will be called with a PortletConfig that contains the bean name
* of the Portlet and the PortletContext that it is running in.
*
* <p>Before destruction of the bean instance, the Portlet <code>destroy</code>
* will be called.
*
* <p><b>Note that this post-processor does not support Portlet initialization
* parameters.</b> Bean instances that implement the Portlet interface are
* supposed to be configured like any other Spring bean, that is, through
* constructor arguments or bean properties.
*
* <p>For reuse of a Portlet implementation in a plain Portlet container and as
* a bean in a Spring context, consider deriving from Spring's GenericPortletBean
* base class that applies Portlet initialization parameters as bean properties,
* supporting both initialization styles.
*
* <p><b>Alternatively, consider wrapping a Portlet with Spring's
* PortletWrappingController.</b> This is particularly appropriate for
* existing Portlet classes, allowing to specify Portlet initialization
* parameters etc.
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see javax.portlet.Portlet
* @see javax.portlet.PortletConfig
* @see SimplePortletHandlerAdapter
* @see org.springframework.web.portlet.GenericPortletBean
* @see org.springframework.web.portlet.mvc.PortletWrappingController
*/
public class SimplePortletPostProcessor
implements DestructionAwareBeanPostProcessor, PortletContextAware, PortletConfigAware {
private boolean useSharedPortletConfig = true;
private PortletContext portletContext;
private PortletConfig portletConfig;
/**
* Set whether to use the shared PortletConfig object passed in
* through <code>setPortletConfig</code>, if available.
* <p>Default is "true". Turn this setting to "false" to pass in
* a mock PortletConfig object with the bean name as portlet name,
* holding the current PortletContext.
* @see #setPortletConfig
*/
public void setUseSharedPortletConfig(boolean useSharedPortletConfig) {
this.useSharedPortletConfig = useSharedPortletConfig;
}
public void setPortletContext(PortletContext portletContext) {
this.portletContext = portletContext;
}
public void setPortletConfig(PortletConfig portletConfig) {
this.portletConfig = portletConfig;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Portlet) {
PortletConfig config = this.portletConfig;
if (config == null || !this.useSharedPortletConfig) {
config = new DelegatingPortletConfig(beanName, this.portletContext, this.portletConfig);
}
try {
((Portlet) bean).init(config);
}
catch (PortletException ex) {
throw new BeanInitializationException("Portlet.init threw exception", ex);
}
}
return bean;
}
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if (bean instanceof Portlet) {
((Portlet) bean).destroy();
}
}
/**
* Internal implementation of the PortletConfig interface, to be passed
* to the wrapped servlet.
*/
private static class DelegatingPortletConfig implements PortletConfig {
private final String portletName;
private final PortletContext portletContext;
private final PortletConfig portletConfig;
public DelegatingPortletConfig(String portletName, PortletContext portletContext, PortletConfig portletConfig) {
this.portletName = portletName;
this.portletContext = portletContext;
this.portletConfig = portletConfig;
}
public String getPortletName() {
return portletName;
}
public PortletContext getPortletContext() {
return portletContext;
}
public String getInitParameter(String paramName) {
return null;
}
public Enumeration getInitParameterNames() {
return Collections.enumeration(Collections.EMPTY_SET);
}
public ResourceBundle getResourceBundle(Locale locale) {
return portletConfig == null ? null : portletConfig.getResourceBundle(locale);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import java.io.IOException;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSecurityException;
/**
* Interceptor that checks the authorization of the current user via the
* user's roles, as evaluated by PortletRequest's isUserInRole method.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see javax.portlet.PortletRequest#isUserInRole
*/
public class UserRoleAuthorizationInterceptor extends HandlerInterceptorAdapter {
private String[] authorizedRoles;
/**
* Set the roles that this interceptor should treat as authorized.
* @param authorizedRoles array of role names
*/
public final void setAuthorizedRoles(String[] authorizedRoles) {
this.authorizedRoles = authorizedRoles;
}
public final boolean preHandle(PortletRequest request, PortletResponse response, Object handler)
throws PortletException, IOException {
if (this.authorizedRoles != null) {
for (int i = 0; i < this.authorizedRoles.length; i++) {
if (request.isUserInRole(this.authorizedRoles[i])) {
return true;
}
}
}
handleNotAuthorized(request, response, handler);
return false;
}
/**
* Handle a request that is not authorized according to this interceptor.
* Default implementation throws a new PortletSecurityException.
* <p>This method can be overridden to write a custom message, forward or
* redirect to some error page or login page, or throw a PortletException.
* @param request current portlet request
* @param response current portlet response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @throws javax.portlet.PortletException if there is an internal error
* @throws java.io.IOException in case of an I/O error when writing the response
*/
protected void handleNotAuthorized(PortletRequest request, PortletResponse response, Object handler)
throws PortletException, IOException {
throw new PortletSecurityException("Request not authorized");
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.handler;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.portlet.HandlerInterceptor;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.context.PortletWebRequest;
/**
* Adapter that implements the Portlet HandlerInterceptor interface
* and wraps an underlying WebRequestInterceptor.
*
* <p><b>NOTE:</b> The WebRequestInterceptor is by default only applied to the Portlet
* <b>render</b> phase, which is dealing with preparing and rendering a Portlet view.
* The Portlet action phase will only be intercepted with WebRequestInterceptor calls
* if the <code>renderPhaseOnly</code> flag is explicitly set to <code>false</code>.
* In general, it is recommended to use the Portlet-specific HandlerInterceptor
* mechanism for differentiating between action and render interception.
*
* @author Juergen Hoeller
* @since 2.0
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see org.springframework.web.portlet.HandlerInterceptor
*/
public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
private final WebRequestInterceptor requestInterceptor;
private final boolean renderPhaseOnly;
/**
* Create a new WebRequestHandlerInterceptorAdapter for the given WebRequestInterceptor,
* applying to the render phase only.
* @param requestInterceptor the WebRequestInterceptor to wrap
*/
public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor) {
this(requestInterceptor, true);
}
/**
* Create a new WebRequestHandlerInterceptorAdapter for the given WebRequestInterceptor.
* @param requestInterceptor the WebRequestInterceptor to wrap
* @param renderPhaseOnly whether to apply to the render phase only (<code>true</code>)
* or to the action phase as well (<code>false</code>)
*/
public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor, boolean renderPhaseOnly) {
Assert.notNull(requestInterceptor, "WebRequestInterceptor must not be null");
this.requestInterceptor = requestInterceptor;
this.renderPhaseOnly = renderPhaseOnly;
}
public boolean preHandleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception {
if (!this.renderPhaseOnly) {
this.requestInterceptor.preHandle(new PortletWebRequest(request));
}
return true;
}
public void afterActionCompletion(
ActionRequest request, ActionResponse response, Object handler, Exception ex) throws Exception {
if (!this.renderPhaseOnly) {
this.requestInterceptor.afterCompletion(new PortletWebRequest(request), ex);
}
}
public boolean preHandleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception {
this.requestInterceptor.preHandle(new PortletWebRequest(request));
return true;
}
public void postHandleRender(
RenderRequest request, RenderResponse response, Object handler, ModelAndView modelAndView) throws Exception {
this.requestInterceptor.postHandle(new PortletWebRequest(request),
(modelAndView != null ? modelAndView.getModelMap() : null));
}
public void afterRenderCompletion(
RenderRequest request, RenderResponse response, Object handler, Exception ex) throws Exception {
this.requestInterceptor.afterCompletion(new PortletWebRequest(request), ex);
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Provides standard HandlerMapping implementations,
including abstract base classes for custom implementations.
</body>
</html>

View File

@ -0,0 +1,200 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.multipart;
import java.util.List;
import javax.portlet.ActionRequest;
import javax.portlet.PortletContext;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.portlet.PortletFileUpload;
import org.apache.commons.fileupload.portlet.PortletRequestContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsFileUploadSupport;
import org.springframework.web.portlet.context.PortletContextAware;
import org.springframework.web.portlet.util.PortletUtils;
/**
* {@link PortletMultipartResolver} implementation for
* <a href="http://jakarta.apache.org/commons/fileupload">Jakarta Commons FileUpload</a>
* 1.1 or above. Commons FileUpload 1.2 or above is recommended.
*
* <p>Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as
* bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding
* PortletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold",
* "headerEncoding") for details in terms of defaults and accepted values.
*
* <p>Saves temporary files to the portlet container's temporary directory.
* Needs to be initialized <i>either</i> by an application context <i>or</i>
* via the constructor that takes a PortletContext (for standalone usage).
*
* @author Juergen Hoeller
* @since 2.0
* @see #CommonsPortletMultipartResolver(javax.portlet.PortletContext)
* @see #setResolveLazily
* @see org.springframework.web.multipart.commons.CommonsMultipartResolver
* @see org.apache.commons.fileupload.portlet.PortletFileUpload
* @see org.apache.commons.fileupload.disk.DiskFileItemFactory
*/
public class CommonsPortletMultipartResolver extends CommonsFileUploadSupport
implements PortletMultipartResolver, PortletContextAware {
private final boolean commonsFileUpload12Present =
ClassUtils.hasMethod(PortletFileUpload.class, "isMultipartContent", new Class[] {ActionRequest.class});
private boolean resolveLazily = false;
/**
* Constructor for use as bean. Determines the portlet container's
* temporary directory via the PortletContext passed in as through the
* PortletContextAware interface (typically by an ApplicationContext).
* @see #setPortletContext
* @see org.springframework.web.portlet.context.PortletContextAware
*/
public CommonsPortletMultipartResolver() {
super();
}
/**
* Constructor for standalone usage. Determines the portlet container's
* temporary directory via the given PortletContext.
* @param portletContext the PortletContext to use
*/
public CommonsPortletMultipartResolver(PortletContext portletContext) {
this();
setPortletContext(portletContext);
}
/**
* Set whether to resolve the multipart request lazily at the time of
* file or parameter access.
* <p>Default is "false", resolving the multipart elements immediately, throwing
* corresponding exceptions at the time of the {@link #resolveMultipart} call.
* Switch this to "true" for lazy multipart parsing, throwing parse exceptions
* once the application attempts to obtain multipart files or parameters.
*/
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
/**
* Initialize the underlying <code>org.apache.commons.fileupload.portlet.PortletFileUpload</code>
* instance. Can be overridden to use a custom subclass, e.g. for testing purposes.
* @return the new PortletFileUpload instance
*/
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
return new PortletFileUpload(fileItemFactory);
}
public void setPortletContext(PortletContext portletContext) {
if (!isUploadTempDirSpecified()) {
getFileItemFactory().setRepository(PortletUtils.getTempDir(portletContext));
}
}
public boolean isMultipart(ActionRequest request) {
if (request == null) {
return false;
}
else if (commonsFileUpload12Present) {
return PortletFileUpload.isMultipartContent(request);
}
else {
return PortletFileUpload.isMultipartContent(new PortletRequestContext(request));
}
}
public MultipartActionRequest resolveMultipart(final ActionRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new DefaultMultipartActionRequest(request) {
protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
}
};
}
else {
MultipartParsingResult parsingResult = parseRequest(request);
return new DefaultMultipartActionRequest(
request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters());
}
}
/**
* Parse the given portlet request, resolving its multipart elements.
* @param request the request to parse
* @return the parsing result
* @throws MultipartException if multipart resolution failed.
*/
protected MultipartParsingResult parseRequest(ActionRequest request) throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
try {
List fileItems = ((PortletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart portlet request", ex);
}
}
/**
* Determine the encoding for the given request.
* Can be overridden in subclasses.
* <p>The default implementation checks the request encoding,
* falling back to the default encoding specified for this resolver.
* @param request current portlet request
* @return the encoding for the request (never <code>null</code>)
* @see javax.portlet.ActionRequest#getCharacterEncoding
* @see #setDefaultEncoding
*/
protected String determineEncoding(ActionRequest request) {
String encoding = request.getCharacterEncoding();
if (encoding == null) {
encoding = getDefaultEncoding();
}
return encoding;
}
public void cleanupMultipart(MultipartActionRequest request) {
if (request != null) {
try {
cleanupFileItems(request.getFileMap().values());
}
catch (Throwable ex) {
logger.warn("Failed to perform multipart cleanup for portlet request", ex);
}
}
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.multipart;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.portlet.ActionRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.portlet.util.ActionRequestWrapper;
/**
* Default implementation of the {@link MultipartActionRequest} interface.
* Provides management of pre-generated parameter values.
*
* @author Juergen Hoeller
* @since 2.0
* @see PortletMultipartResolver
*/
public class DefaultMultipartActionRequest extends ActionRequestWrapper implements MultipartActionRequest {
private Map multipartFiles;
private Map multipartParameters;
/**
* Wrap the given Portlet ActionRequest in a MultipartActionRequest.
* @param request the request to wrap
* @param multipartFiles a map of the multipart files
* @param multipartParameters a map of the parameters to expose,
* with Strings as keys and String arrays as values
*/
public DefaultMultipartActionRequest(ActionRequest request, Map multipartFiles, Map multipartParameters) {
super(request);
setMultipartFiles(multipartFiles);
setMultipartParameters(multipartParameters);
}
/**
* Wrap the given Portlet ActionRequest in a MultipartActionRequest.
* @param request the request to wrap
*/
protected DefaultMultipartActionRequest(ActionRequest request) {
super(request);
}
public Iterator getFileNames() {
return getMultipartFiles().keySet().iterator();
}
public MultipartFile getFile(String name) {
return (MultipartFile) getMultipartFiles().get(name);
}
public Map getFileMap() {
return getMultipartFiles();
}
public Enumeration getParameterNames() {
Set paramNames = new HashSet();
Enumeration paramEnum = super.getParameterNames();
while (paramEnum.hasMoreElements()) {
paramNames.add(paramEnum.nextElement());
}
paramNames.addAll(getMultipartParameters().keySet());
return Collections.enumeration(paramNames);
}
public String getParameter(String name) {
String[] values = (String[]) getMultipartParameters().get(name);
if (values != null) {
return (values.length > 0 ? values[0] : null);
}
return super.getParameter(name);
}
public String[] getParameterValues(String name) {
String[] values = (String[]) getMultipartParameters().get(name);
if (values != null) {
return values;
}
return super.getParameterValues(name);
}
public Map getParameterMap() {
Map paramMap = new HashMap();
paramMap.putAll(super.getParameterMap());
paramMap.putAll(getMultipartParameters());
return paramMap;
}
/**
* Set a Map with parameter names as keys and MultipartFile objects as values.
* To be invoked by subclasses on initialization.
*/
protected final void setMultipartFiles(Map multipartFiles) {
this.multipartFiles = Collections.unmodifiableMap(multipartFiles);
}
/**
* Obtain the MultipartFile Map for retrieval,
* lazily initializing it if necessary.
* @see #initializeMultipart()
*/
protected Map getMultipartFiles() {
if (this.multipartFiles == null) {
initializeMultipart();
}
return this.multipartFiles;
}
/**
* Set a Map with parameter names as keys and String array objects as values.
* To be invoked by subclasses on initialization.
*/
protected final void setMultipartParameters(Map multipartParameters) {
this.multipartParameters = multipartParameters;
}
/**
* Obtain the multipart parameter Map for retrieval,
* lazily initializing it if necessary.
* @see #initializeMultipart()
*/
protected Map getMultipartParameters() {
if (this.multipartParameters == null) {
initializeMultipart();
}
return this.multipartParameters;
}
/**
* Lazily initialize the multipart request, if possible.
* Only called if not already eagerly initialized.
*/
protected void initializeMultipart() {
throw new IllegalStateException("Multipart request not initialized");
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.multipart;
import javax.portlet.ActionRequest;
import org.springframework.web.multipart.MultipartRequest;
/**
* Interface which provides additional methods for dealing with multipart
* content within a portlet request, allowing to access uploaded files.
* Implementations also need to override the standard ActionRequest
* methods for parameter access, making multipart parameters available.
*
* <p>A concrete implementation is {@link DefaultMultipartActionRequest}.
*
* @author Juergen Hoeller
* @since 2.0
* @see PortletMultipartResolver
* @see org.springframework.web.multipart.MultipartFile
* @see javax.portlet.ActionRequest#getParameter
* @see javax.portlet.ActionRequest#getParameterNames
* @see javax.portlet.ActionRequest#getParameterMap
*/
public interface MultipartActionRequest extends ActionRequest, MultipartRequest {
}

View File

@ -0,0 +1,117 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.multipart;
import javax.portlet.ActionRequest;
import org.springframework.web.multipart.MultipartException;
/**
* Portlet version of Spring's multipart resolution strategy for file uploads
* as defined in <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
*
* <p>Implementations are typically usable both within any application context
* and standalone.
*
* <p>There is one concrete implementation included in Spring:
* <ul>
* <li>{@link org.springframework.web.multipart.commons.CommonsMultipartResolver}
* for Jakarta Commons FileUpload
* </ul>
*
* <p>There is no default resolver implementation used for Spring
* {@link org.springframework.web.portlet.DispatcherPortlet DispatcherPortlets},
* as an application might choose to parse its multipart requests itself. To
* define an implementation, create a bean with the id
* {@link org.springframework.web.portlet.DispatcherPortlet#MULTIPART_RESOLVER_BEAN_NAME "portletMultipartResolver"}
* in a <code>DispatcherPortlet's</code> application context. Such a resolver
* gets applied to all requests handled by that <code>DispatcherPortlet</code>.
*
* <p>If a <code>DispatcherPortlet</code> detects a multipart request, it will
* resolve it via the configured
* {@link org.springframework.web.multipart.MultipartResolver} and pass on a
* wrapped Portlet {@link ActionRequest}.
* {@link org.springframework.web.portlet.mvc.Controller Controllers} can then
* cast their given request to the {@link MultipartActionRequest} interface,
* being able to access <code>MultipartFiles</code>. Note that this cast is
* only supported in case of an actual multipart request.
*
* <pre class="code"> public void handleActionRequest(ActionRequest request, ActionResponse response) {
* MultipartActionRequest multipartRequest = (MultipartActionRequest) request;
* MultipartFile multipartFile = multipartRequest.getFile("image");
* ...
* }</pre>
*
* Instead of direct access, command or form controllers can register a
* {@link org.springframework.web.multipart.support.ByteArrayMultipartFileEditor}
* or {@link org.springframework.web.multipart.support.StringMultipartFileEditor}
* with their data binder, to automatically apply multipart content to command
* bean properties.
*
* <p>Note: There is hardly ever a need to access the
* <code>MultipartResolver</code> itself from application code. It will simply
* do its work behind the scenes, making <code>MultipartActionRequests</code>
* available to controllers.
*
* @author Juergen Hoeller
* @since 2.0
* @see MultipartActionRequest
* @see org.springframework.web.multipart.MultipartFile
* @see CommonsPortletMultipartResolver
* @see org.springframework.web.multipart.support.ByteArrayMultipartFileEditor
* @see org.springframework.web.multipart.support.StringMultipartFileEditor
* @see org.springframework.web.portlet.DispatcherPortlet
*/
public interface PortletMultipartResolver {
/**
* Determine if the given request contains multipart content.
* <p>Will typically check for content type
* "<code>multipart/form-data</code>", but the actually accepted requests
* might depend on the capabilities of the resolver implementation.
* @param request the portlet request to be evaluated
* @return whether the request contains multipart content
*/
boolean isMultipart(ActionRequest request);
/**
* Parse the given portlet request into multipart files and parameters,
* and wrap the request inside a MultipartActionRequest object
* that provides access to file descriptors and makes contained
* parameters accessible via the standard PortletRequest methods.
* @param request the portlet request to wrap (must be of a multipart content type)
* @return the wrapped portlet request
* @throws org.springframework.web.multipart.MultipartException if the portlet request
* is not multipart, or if implementation-specific problems are encountered
* (such as exceeding file size limits)
* @see org.springframework.web.portlet.multipart.MultipartActionRequest#getFile
* @see org.springframework.web.portlet.multipart.MultipartActionRequest#getFileNames
* @see org.springframework.web.portlet.multipart.MultipartActionRequest#getFileMap
* @see javax.portlet.ActionRequest#getParameter
* @see javax.portlet.ActionRequest#getParameterNames
* @see javax.portlet.ActionRequest#getParameterMap
*/
MultipartActionRequest resolveMultipart(ActionRequest request) throws MultipartException;
/**
* Cleanup any resources used for the multipart handling,
* such as storage for any uploaded file(s).
* @param request the request to cleanup resources for
*/
void cleanupMultipart(MultipartActionRequest request);
}

View File

@ -0,0 +1,10 @@
<html>
<body>
Multipart resolution framework for handling file uploads.
Provides a PortletMultipartResolver strategy interface,
and a generic extension of the ActionRequest interface
for accessing multipart files in web application code.
</body>
</html>

View File

@ -0,0 +1,209 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.validation.BindException;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.util.PortletUtils;
/**
* <p>Abstract base class for custom command controllers. Autopopulates a
* command bean from the request. For command validation, a validator
* (property inherited from BaseCommandController) can be used.</p>
*
* <p>This command controller should preferrable not be used to handle form
* submission, because functionality for forms is more offered in more
* detail by the {@link org.springframework.web.portlet.mvc.AbstractFormController
* AbstractFormController} and its corresponding implementations.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <i>none</i> (so only those available in superclass).</p>
*
* <p><b><a name="workflow">Workflow
* (<a name="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see #setCommandClass
* @see #setCommandName
* @see #setValidator
*/
public abstract class AbstractCommandController extends BaseCommandController {
/**
* This render parameter is used to indicate forward to the render phase
* that a valid command (and errors) object is in the session.
*/
private static final String COMMAND_IN_SESSION_PARAMETER = "command-in-session";
private static final String TRUE = Boolean.TRUE.toString();
/**
* Create a new AbstractCommandController.
*/
public AbstractCommandController() {
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
*/
public AbstractCommandController(Class commandClass) {
setCommandClass(commandClass);
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
* @param commandName name of the command bean
*/
public AbstractCommandController(Class commandClass, String commandName) {
setCommandClass(commandClass);
setCommandName(commandName);
}
protected final void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception {
// Create the command object.
Object command = getCommand(request);
// Compute the errors object.
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
// Actually handle the action.
handleAction(request, response, command, errors);
// Pass the command and errors forward to the render phase.
setRenderCommandAndErrors(request, command, errors);
setCommandInSession(response);
}
protected final ModelAndView handleRenderRequestInternal(
RenderRequest request, RenderResponse response) throws Exception {
Object command = null;
BindException errors = null;
// Get the command and errors objects from the session, if they exist.
if (isCommandInSession(request)) {
logger.debug("Render phase obtaining command and errors objects from session");
command = getRenderCommand(request);
errors = getRenderErrors(request);
}
else {
logger.debug("Render phase creating new command and errors objects");
command = getCommand(request);
PortletRequestDataBinder binder = bindAndValidate(request, command);
errors = new BindException(binder.getBindingResult());
}
return handleRender(request, response, command, errors);
}
/**
* Template method for request handling, providing a populated and validated instance
* of the command class, and an Errors object containing binding and validation errors.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current action request
* @param response current action response
* @param command the populated command object
* @param errors validation errors holder
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract void handleAction(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception;
/**
* Template method for render request handling, providing a populated and validated instance
* of the command class, and an Errors object containing binding and validation errors.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current render request
* @param response current render response
* @param command the populated command object
* @param errors validation errors holder
* @return a ModelAndView to render, or null if handled directly
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView handleRender(
RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception;
/**
* Return the name of the render parameter that indicates there
* is a valid command (and errors) object in the session.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getCommandInSessionParameterName() {
return COMMAND_IN_SESSION_PARAMETER;
}
/**
* Set the action response parameter that indicates there is a
* command (and errors) object in the session for the render phase.
* @param response the current action response
* @see #getCommandInSessionParameterName
* @see #isCommandInSession
*/
protected final void setCommandInSession(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getCommandInSessionParameterName() +
"] to indicate a valid command (and errors) object are in the session");
}
try {
response.setRenderParameter(getCommandInSessionParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Determine if there is a valid command (and errors) object in the
* session for this render request.
* @param request current render request
* @return if there is a valid command object in the session
* @see #getCommandInSessionParameterName
* @see #setCommandInSession
*/
protected boolean isCommandInSession(RenderRequest request) {
return (TRUE.equals(request.getParameter(getCommandInSessionParameterName())) &&
PortletUtils.getSessionAttribute(request, getRenderCommandSessionAttributeName()) != null);
}
}

View File

@ -0,0 +1,251 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.WindowState;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.handler.PortletContentGenerator;
import org.springframework.web.portlet.util.PortletUtils;
/**
* Convenient superclass for controller implementations, using the Template
* Method design pattern.
*
* <p>As stated in the {@link Controller Controller}
* interface, a lot of functionality is already provided by certain abstract
* base controllers. The AbstractController is one of the most important
* abstract base controller providing basic features such controlling if a
* session is required and render caching.
*
* <p><b><a name="workflow">Workflow
* (<a href="Controller.html#workflow">and that defined by interface</a>):</b><br>
* <ol>
* <li>If this is an action request, {@link #handleActionRequest handleActionRequest}
* will be called by the DispatcherPortlet once to perform the action defined by this
* controller.</li>
* <li>If a session is required, try to get it (PortletException if not found).</li>
* <li>Call method {@link #handleActionRequestInternal handleActionRequestInternal},
* (optionally synchronizing around the call on the PortletSession),
* which should be overridden by extending classes to provide actual functionality to
* perform the desired action of the controller. This will be executed only once.</li>
* <li>For a straight render request, or the render phase of an action request (assuming the
* same controller is called for the render phase -- see tip below),
* {@link #handleRenderRequest handleRenderRequest} will be called by the DispatcherPortlet
* repeatedly to render the display defined by this controller.</li>
* <li>If a session is required, try to get it (PortletException if none found).</li>
* <li>It will control caching as defined by the cacheSeconds property.</li>
* <li>Call method {@link #handleRenderRequestInternal handleRenderRequestInternal},
* (optionally synchronizing around the call on the PortletSession),
* which should be overridden by extending classes to provide actual functionality to
* return {@link org.springframework.web.portlet.ModelAndView ModelAndView} objects.
* This will be executed repeatedly as the portal updates the current displayed page.</li>
* </ol>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="Controller.html#config">and those defined by interface</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></th>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>requireSession</td>
* <td>false</td>
* <td>whether a session should be required for requests to be able to
* be handled by this controller. This ensures, derived controller
* can - without fear of Nullpointers - call request.getSession() to
* retrieve a session. If no session can be found while processing
* the request, a PortletException will be thrown</td>
* </tr>
* <tr>
* <td>synchronizeOnSession</td>
* <td>false</td>
* <td>whether the calls to <code>handleRenderRequestInternal</code> and
* <code>handleRenderRequestInternal</code> should be
* synchronized around the PortletSession, to serialize invocations
* from the same client. No effect if there is no PortletSession.
* </td>
* </tr>
* <tr>
* <td>cacheSeconds</td>
* <td>-1</td>
* <td>indicates the amount of seconds to specify caching is allowed in
* the render response generatedby this request. 0 (zero) will indicate
* no caching is allowed at all, -1 (the default) will not override the
* portlet configuration and any positive number will cause the render
* response to declare the amount indicated as seconds to cache the content</td>
* </tr>
* <tr>
* <td>renderWhenMinimized</td>
* <td>false</td>
* <td>whether should be rendered when the portlet is in a minimized state --
* will return null for the ModelandView when the portlet is minimized
* and this is false</td>
* </tr>
* </table>
*
* <p><b>TIP:</b> The controller mapping will be run twice by the PortletDispatcher for
* action requests -- once for the action phase and again for the render phase. You can
* reach the render phase of a different controller by simply changing the values for the
* criteria your mapping is using, such as portlet mode or a request parameter, during the
* action phase of your controller. This is very handy since redirects within the portlet
* are apparently impossible. Before doing this, it is usually wise to call
* <code>clearAllRenderParameters</code> and then explicitly set all the parameters that
* you want the new controller to see. This avoids unexpected parameters from being passed
* to the render phase of the second controller, such as the parameter indicating a form
* submit ocurred in an <code>AbstractFormController</code>.
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public abstract class AbstractController extends PortletContentGenerator implements Controller {
private boolean synchronizeOnSession = false;
private boolean renderWhenMinimized = false;
/**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of the <code>handleActionRequestInternal</code>
* method will get synchronized if this flag is "true". The best available
* session mutex will be used for the synchronization; ideally, this will
* be a mutex exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the PortletSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see #handleActionRequestInternal
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession)
*/
public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
/**
* Return whether controller execution should be synchronized on the session.
*/
public final boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
/**
* Set if the controller should render an view when the portlet is in
* a minimized window. The default is false.
* @see javax.portlet.RenderRequest#getWindowState
* @see javax.portlet.WindowState#MINIMIZED
*/
public final void setRenderWhenMinimized(boolean renderWhenMinimized) {
this.renderWhenMinimized = renderWhenMinimized;
}
/**
* Return whether controller will render when portlet is minimized.
*/
public final boolean isRenderWhenMinimized() {
return this.renderWhenMinimized;
}
public void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception {
// Delegate to PortletContentGenerator for checking and preparing.
check(request, response);
// Execute in synchronized block if required.
if (this.synchronizeOnSession) {
PortletSession session = request.getPortletSession(false);
if (session != null) {
synchronized (session) {
handleActionRequestInternal(request, response);
return;
}
}
}
handleActionRequestInternal(request, response);
}
public ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response) throws Exception {
// If the portlet is minimized and we don't want to render then return null.
if (WindowState.MINIMIZED.equals(request.getWindowState()) && !this.renderWhenMinimized) {
return null;
}
// Delegate to PortletContentGenerator for checking and preparing.
checkAndPrepare(request, response);
// Execute in synchronized block if required.
if (this.synchronizeOnSession) {
PortletSession session = request.getPortletSession(false);
if (session != null) {
Object mutex = PortletUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRenderRequestInternal(request, response);
}
}
}
return handleRenderRequestInternal(request, response);
}
/**
* Subclasses are meant to override this method if the controller
* is expected to handle action requests. The contract is the same as
* for <code>handleActionRequest</code>.
* <p>The default implementation throws a PortletException.
* @see #handleActionRequest
* @see #handleRenderRequestInternal
*/
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception {
throw new PortletException("[" + getClass().getName() + "] does not handle action requests");
}
/**
* Subclasses are meant to override this method if the controller
* is expected to handle render requests. The contract is the same as
* for <code>handleRenderRequest</code>.
* <p>The default implementation throws a PortletException.
* @see #handleRenderRequest
* @see #handleActionRequestInternal
*/
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception {
throw new PortletException("[" + getClass().getName() + "] does not handle render requests");
}
}

View File

@ -0,0 +1,969 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import java.util.Arrays;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
/**
* <p>Form controller that auto-populates a form bean from the request.
* This, either using a new bean instance per request, or using the same bean
* when the <code>sessionForm</code> property has been set to
* <code>true</code>.</p>
*
* <p>This class is the base class for both framework subclasses such as
* {@link SimpleFormController} and {@link AbstractWizardFormController}
* and custom form controllers that you may provide yourself.</p>
*
* <p>A form-input view and an after-submission view have to be provided
* programmatically. To provide those views using configuration properties,
* use the {@link SimpleFormController}.</p>
*
* <p>Subclasses need to override <code>showForm</code> to prepare the form view,
* <code>processFormSubmission</code> to handle submit requests, and
* <code>renderFormSubmission</code> to display the results of the submit.
* For the latter two methods, binding errors like type mismatches will be
* reported via the given "errors" holder. For additional custom form validation,
* a validator (property inherited from BaseCommandController) can be used,
* reporting via the same "errors" instance.</p>
*
* <p>Comparing this Controller to the Struts notion of the <code>Action</code>
* shows us that with Spring, you can use any ordinary JavaBeans or database-
* backed JavaBeans without having to implement a framework-specific class
* (like Struts' <code>ActionForm</code>). More complex properties of JavaBeans
* (Dates, Locales, but also your own application-specific or compound types)
* can be represented and submitted to the controller, by using the notion of
* a <code>java.beans.PropertyEditors</code>. For more information on that
* subject, see the workflow of this controller and the explanation of the
* {@link BaseCommandController BaseCommandController}.</p>
*
* <p>This controller is different from it's servlet counterpart in that it must take
* into account the two phases of a portlet request: the action phase and the render
* phase. See the JSR-168 spec for more details on these two phases.
* Be especially aware that the action phase is called only once, but that the
* render phase will be called repeatedly by the portal; it does this every time
* the page containing the portlet is updated, even if the activity is in some other
* portlet. (This is not quite true, the portal can also be told to cache the results of
* the render for a period of time, but assume it is true for programming purposes.)</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li><b>The controller receives a request for a new form (typically a
* Render Request only).</b> The render phase will proceed to display
* the form as follows.</li>
* <li>Call to {@link #formBackingObject formBackingObject()} which by
* default, returns an instance of the commandClass that has been
* configured (see the properties the superclass exposes), but can also be
* overridden to e.g. retrieve an object from the database (that needs to
* be modified using the form).</li>
* <li>Call to {@link #initBinder initBinder()} which allows you to
* register custom editors for certain fields (often properties of non-
* primitive or non-String types) of the command class. This will render
* appropriate Strings for those property values, e.g. locale-specific
* date strings. </li>
* <li>The {@link PortletRequestDataBinder PortletRequestDataBinder}
* gets applied to populate the new form object with initial request parameters and the
* {@link #onBindOnNewForm(RenderRequest, Object, BindException)} callback method is invoked.
* (<i>only if <code>bindOnNewForm</code> is set to <code>true</code></i>)
* Make sure that the initial parameters do not include the parameter that indicates a
* form submission has occurred.</li>
* <li>Call to {@link #showForm(RenderRequest, RenderResponse,
* BindException) showForm} to return a View that should be rendered
* (typically the view that renders the form). This method has to be
* implemented in subclasses. </li>
* <li>The showForm() implementation will call {@link #referenceData referenceData},
* which you can implement to provide any relevant reference data you might need
* when editing a form (e.g. a List of Locale objects you're going to let the
* user select one from).</li>
* <li>Model gets exposed and view gets rendered, to let the user fill in
* the form.</li>
* <li><b>The controller receives a form submission (typically an Action
* Request).</b> To use a different way of detecting a form submission,
* override the {@link #isFormSubmission isFormSubmission} method.
* The action phase will proceed to process the form submission as follows.</li>
* <li>If <code>sessionForm</code> is not set, {@link #formBackingObject
* formBackingObject} is called to retrieve a form object. Otherwise,
* the controller tries to find the command object which is already bound
* in the session. If it cannot find the object, the action phase does a
* call to {@link #handleInvalidSubmit handleInvalidSubmit} which - by default -
* tries to create a new form object and resubmit the form. It then sets
* a render parameter that will indicate to the render phase that this was
* an invalid submit.</li>
* <li>Still in the action phase of a valid submit, the {@link
* PortletRequestDataBinder PortletRequestDataBinder} gets applied to populate
* the form object with current request parameters.</li>
* <li>Call to {@link #onBind onBind(PortletRequest, Object, Errors)}
* which allows you to do custom processing after binding but before
* validation (e.g. to manually bind request parameters to bean
* properties, to be seen by the Validator).</li>
* <li>If <code>validateOnBinding</code> is set, a registered Validator
* will be invoked. The Validator will check the form object properties,
* and register corresponding errors via the given {@link Errors Errors}
* object.</li>
* <li>Call to {@link #onBindAndValidate onBindAndValidate} which allows
* you to do custom processing after binding and validation (e.g. to
* manually bind request parameters, and to validate them outside a
* Validator).</li>
* <li>Call to {@link #processFormSubmission processFormSubmission}
* to process the submission, with or without binding errors.
* This method has to be implemented in subclasses and will be called
* only once per form submission.</li>
* <li>The portal will then call the render phase of processing the form
* submission. This phase will be called repeatedly by the portal every
* time the page is refreshed. All processing here should take this into
* account. Any one-time-only actions (such as modifying a database) must
* be done in the action phase.</li>
* <li>If the action phase indicated this is an invalid submit, the render
* phase calls {@link #renderInvalidSubmit renderInvalidSubmit} which &ndash;
* also by default &ndash; will render the results of the resubmitted
* form. Be sure to override both <code>handleInvalidSubmit</code> and
* <code>renderInvalidSubmit</code> if you want to change this overall
* behavior.</li>
* <li>Finally, call {@link #renderFormSubmission renderFormSubmission} to
* render the results of the submission, with or without binding errors.
* This method has to be implemented in subclasses and will be called
* repeatedly by the portal.</li>
* </ol>
* </p>
*
* <p>In session form mode, a submission without an existing form object in the
* session is considered invalid, like in the case of a resubmit/reload by the browser.
* The {@link #handleInvalidSubmit handleInvalidSubmit} /
* {@link #renderInvalidSubmit renderInvalidSubmit} methods are invoked then,
* by default trying to resubmit. This can be overridden in subclasses to show
* corresponding messages or to redirect to a new form, in order to avoid duplicate
* submissions. The form object in the session can be considered a transaction token
* in that case.</p>
*
* <p>Make sure that any URLs that take you to your form controller are Render URLs,
* so that it will not try to treat the initial call as a form submission.
* If you use action URLs to link to your controller, you will need to override the
* {@link #isFormSubmission isFormSubmission} method to use a different mechanism for
* determining whether a form has been submitted. Make sure this method will work for
* both the ActionRequest and the RenderRequest objects.</p>
*
* <p>Note that views should never retrieve form beans from the session but always
* from the request, as prepared by the form controller. Remember that some view
* technologies like Velocity cannot even access a HTTP session.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>bindOnNewForm</td>
* <td>false</td>
* <td>Indicates whether to bind portlet request parameters when
* creating a new form. Otherwise, the parameters will only be
* bound on form submission attempts.</td>
* </tr>
* <tr>
* <td>sessionForm</td>
* <td>false</td>
* <td>Indicates whether the form object should be kept in the session
* when a user asks for a new form. This allows you e.g. to retrieve
* an object from the database, let the user edit it, and then persist
* it again. Otherwise, a new command object will be created for each
* request (even when showing the form again after validation errors).</td>
* </tr>
* <tr>
* <td>redirectAction</td>
* <td>false</td>
* <td>Specifies whether <code>processFormSubmission</code> is expected to call
* {@link ActionResponse#sendRedirect ActionResponse.sendRedirect}.
* This is important because some methods may not be called before
* {@link ActionResponse#sendRedirect ActionResponse.sendRedirect} (e.g.
* {@link ActionResponse#setRenderParameter ActionResponse.setRenderParameter}).
* Setting this flag will prevent AbstractFormController from setting render
* parameters that it normally needs for the render phase.
* If this is set true and <code>sendRedirect</code> is not called, then
* <code>processFormSubmission</code> must call
* {@link #setFormSubmit setFormSubmit}.
* Otherwise, the render phase will not realize the form was submitted
* and will simply display a new blank form.</td>
* </tr>
* <tr>
* <td>renderParameters</td>
* <td>null</td>
* <td>An array of parameters that will be passed forward from the action
* phase to the render phase if the form needs to be displayed
* again. These can also be passed forward explicitly by calling
* the <code>passRenderParameters</code> method from any action
* phase method. Abstract descendants of this controller should follow
* similar behavior. If there are parameters you need in
* <code>renderFormSubmission</code>, then you need to pass those
* forward from <code>processFormSubmission</code>. If you override the
* default behavior of invalid submits and you set sessionForm to true,
* then you probably will not need to set this because your parameters
* are only going to be needed on the first request.</td>
* </tr>
* </table>
* </p>
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @author Alef Arendsen
* @author Rob Harrop
* @since 2.0
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see SimpleFormController
* @see AbstractWizardFormController
*/
public abstract class AbstractFormController extends BaseCommandController {
/**
* These render parameters are used to indicate forward to the render phase
* if the form was submitted and if the submission was invalid.
*/
private static final String FORM_SUBMISSION_PARAMETER = "form-submit";
private static final String INVALID_SUBMISSION_PARAMETER = "invalid-submit";
private static final String TRUE = Boolean.TRUE.toString();
private boolean bindOnNewForm = false;
private boolean sessionForm = false;
private boolean redirectAction = false;
private String[] renderParameters = null;
/**
* Create a new AbstractFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
* Note that commandClass doesn't need to be set when overriding
* <code>formBackingObject</code>, as the latter determines the class anyway.
* <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
* @see #setCommandName
* @see #setCommandClass
* @see #setBindOnNewForm
* @see #setSessionForm
* @see #formBackingObject
*/
public AbstractFormController() {
setCacheSeconds(0);
}
/**
* Set if request parameters should be bound to the form object
* in case of a non-submitting request, i.e. a new form.
*/
public final void setBindOnNewForm(boolean bindOnNewForm) {
this.bindOnNewForm = bindOnNewForm;
}
/**
* Return if request parameters should be bound in case of a new form.
*/
public final boolean isBindOnNewForm() {
return this.bindOnNewForm;
}
/**
* Activate/deactivate session form mode. In session form mode,
* the form is stored in the session to keep the form object instance
* between requests, instead of creating a new one on each request.
* <p>This is necessary for either wizard-style controllers that populate a
* single form object from multiple pages, or forms that populate a persistent
* object that needs to be identical to allow for tracking changes.
*/
public final void setSessionForm(boolean sessionForm) {
this.sessionForm = sessionForm;
}
/**
* Return if session form mode is activated.
*/
public final boolean isSessionForm() {
return this.sessionForm;
}
/**
* Specify whether the action phase is expected to call
* {@link ActionResponse#sendRedirect}.
* This information is important because some methods may not be called
* before {@link ActionResponse#sendRedirect}, e.g.
* {@link ActionResponse#setRenderParameter} and
* {@link ActionResponse#setRenderParameters}.
* <p><b>NOTE:</b> Call this at initialization time of your controller:
* either in the constructor or in the bean definition for your controller.
* @see ActionResponse#sendRedirect
*/
public void setRedirectAction(boolean redirectAction) {
this.redirectAction = redirectAction;
}
/**
* Return if {@link ActionResponse#sendRedirect} is
* expected to be called in the action phase.
*/
public boolean isRedirectAction() {
return this.redirectAction;
}
/**
* Specify the list of parameters that should be passed forward
* from the action phase to the render phase whenever the form is
* re-rendered or when {@link #passRenderParameters} is called.
* @see #passRenderParameters
*/
public void setRenderParameters(String[] parameters) {
this.renderParameters = parameters;
}
/**
* Returns the list of parameters that will be passed forward
* from the action phase to the render phase whenever the form is
* rerendered or when {@link #passRenderParameters} is called.
* @return the list of parameters
* @see #passRenderParameters
*/
public String[] getRenderParameters() {
return this.renderParameters;
}
/**
* Handles action phase of two cases: form submissions and showing a new form.
* Delegates the decision between the two to <code>isFormSubmission</code>,
* always treating requests without existing form session attribute
* as new form when using session form mode.
* @see #isFormSubmission
* @see #processFormSubmission
* @see #handleRenderRequestInternal
*/
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception {
// Form submission or new form to show?
if (isFormSubmission(request)) {
// Fetch form object, bind, validate, process submission.
try {
Object command = getCommand(request);
if (logger.isDebugEnabled()) {
logger.debug("Processing valid submit (redirectAction = " + isRedirectAction() + ")");
}
if (!isRedirectAction()) {
setFormSubmit(response);
}
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
processFormSubmission(request, response, command, errors);
setRenderCommandAndErrors(request, command, errors);
return;
}
catch (PortletSessionRequiredException ex) {
// Cannot submit a session form if no form object is in the session.
if (logger.isDebugEnabled()) {
logger.debug("Invalid submit detected: " + ex.getMessage());
}
setFormSubmit(response);
setInvalidSubmit(response);
handleInvalidSubmit(request, response);
return;
}
}
else {
logger.debug("Not a form submit - passing parameters to render phase");
passRenderParameters(request, response);
return;
}
}
/**
* Handles render phase of two cases: form submissions and showing a new form.
* Delegates the decision between the two to <code>isFormSubmission</code>,
* always treating requests without existing form session attribute
* as new form when using session form mode.
* @see #isFormSubmission
* @see #showNewForm
* @see #processFormSubmission
* @see #handleActionRequestInternal
*/
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception {
// Form submission or new form to show?
if (isFormSubmission(request)) {
// If it is an invalid submit then handle it.
if (isInvalidSubmission(request)) {
logger.debug("Invalid submit - calling renderInvalidSubmit");
return renderInvalidSubmit(request, response);
}
// Valid submit -> render.
logger.debug("Valid submit - calling renderFormSubmission");
return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
}
else {
// New form to show: render form view.
return showNewForm(request, response);
}
}
/**
* Determine if the given request represents a form submission.
* <p>The default implementation checks to see if this is an ActionRequest
* and treats all action requests as form submission. During the action
* phase it will pass forward a render parameter to indicate to the render
* phase that this is a form submission. This method can check both
* kinds of requests and indicate if this is a form submission.
* <p>Subclasses can override this to use a custom strategy, e.g. a specific
* request parameter (assumably a hidden field or submit button name). Make
* sure that the override can handle both ActionRequest and RenderRequest
* objects properly.
* @param request current request
* @return if the request represents a form submission
*/
protected boolean isFormSubmission(PortletRequest request) {
return (request instanceof ActionRequest || TRUE.equals(request.getParameter(getFormSubmitParameterName())));
}
/**
* Determine if the given request represents an invalid form submission.
*/
protected boolean isInvalidSubmission(PortletRequest request) {
return TRUE.equals(request.getParameter(getInvalidSubmitParameterName()));
}
/**
* Return the name of the render parameter that indicates this
* was a form submission.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getFormSubmitParameterName() {
return FORM_SUBMISSION_PARAMETER;
}
/**
* Return the name of the render parameter that indicates this
* was an invalid form submission.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getInvalidSubmitParameterName() {
return INVALID_SUBMISSION_PARAMETER;
}
/**
* Set the action response parameter that indicates this in a form submission.
* @param response the current action response
* @see #getFormSubmitParameterName()
*/
protected final void setFormSubmit(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getFormSubmitParameterName() +
"] to indicate this is a form submission");
}
try {
response.setRenderParameter(getFormSubmitParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Set the action response parameter that indicates this in an invalid submission.
* @param response the current action response
* @see #getInvalidSubmitParameterName()
*/
protected final void setInvalidSubmit(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getInvalidSubmitParameterName() +
"] to indicate this is an invalid submission");
}
try {
response.setRenderParameter(getInvalidSubmitParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Return the name of the PortletSession attribute that holds the form object
* for this form controller.
* <p>The default implementation delegates to the
* <code>getFormSessionAttributeName</code> version without arguments.
* @param request current HTTP request
* @return the name of the form session attribute,
* or <code>null</code> if not in session form mode
* @see #getFormSessionAttributeName()
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getFormSessionAttributeName(PortletRequest request) {
return getFormSessionAttributeName();
}
/**
* Return the name of the PortletSession attribute that holds the form object
* for this form controller.
* <p>Default is an internal name, of no relevance to applications, as the form
* session attribute is not usually accessed directly. Can be overridden to use
* an application-specific attribute name, which allows other code to access
* the session attribute directly.
* @return the name of the form session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getFormSessionAttributeName() {
return getClass().getName() + ".FORM." + getCommandName();
}
/**
* Pass the specified list of action request parameters to the render phase
* by putting them into the action response object. This may not be called
* when the action will call will call
* {@link ActionResponse#sendRedirect sendRedirect}.
* @param request the current action request
* @param response the current action response
* @see ActionResponse#setRenderParameter
*/
protected void passRenderParameters(ActionRequest request, ActionResponse response) {
if (this.renderParameters == null) {
return;
}
try {
for (int i = 0; i < this.renderParameters.length; i++) {
String paramName = this.renderParameters[i];
String paramValues[] = request.getParameterValues(paramName);
if (paramValues != null) {
if (logger.isDebugEnabled()) {
logger.debug("Passing parameter to render phase '" + paramName + "' = " +
(paramValues == null ? "NULL" : Arrays.asList(paramValues).toString()));
}
response.setRenderParameter(paramName, paramValues);
}
}
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Show a new form. Prepares a backing object for the current form
* and the given request, including checking its validity.
* @param request current render request
* @param response current render response
* @return the prepared form view
* @throws Exception in case of an invalid new form object
* @see #getErrorsForNewForm
*/
protected final ModelAndView showNewForm(RenderRequest request, RenderResponse response)
throws Exception {
logger.debug("Displaying new form");
return showForm(request, response, getErrorsForNewForm(request));
}
/**
* Create a BindException instance for a new form.
* Called by <code>showNewForm</code>.
* <p>Can be used directly when intending to show a new form but with
* special errors registered on it (for example, on invalid submit).
* Usually, the resulting BindException will be passed to
* <code>showForm</code>, after registering the errors on it.
* @param request current render request
* @return the BindException instance
* @throws Exception in case of an invalid new form object
*/
protected final BindException getErrorsForNewForm(RenderRequest request) throws Exception {
// Create form-backing object for new form
Object command = formBackingObject(request);
if (command == null) {
throw new PortletException("Form object returned by formBackingObject() must not be null");
}
if (!checkCommand(command)) {
throw new PortletException("Form object returned by formBackingObject() must match commandClass");
}
// Bind without validation, to allow for prepopulating a form, and for
// convenient error evaluation in views (on both first attempt and resubmit).
PortletRequestDataBinder binder = createBinder(request, command);
BindException errors = new BindException(binder.getBindingResult());
if (isBindOnNewForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Binding to new form");
}
binder.bind(request);
onBindOnNewForm(request, command, errors);
}
// Return BindException object that resulted from binding.
return errors;
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* Called when preparing a new form if <code>bindOnNewForm</code> is <code>true</code>.
* <p>Default implementation delegates to <code>onBindOnNewForm(request, command)</code>.
* @param request current render request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(RenderRequest, Object)
* @see #setBindOnNewForm
*/
protected void onBindOnNewForm(RenderRequest request, Object command, BindException errors)
throws Exception {
onBindOnNewForm(request, command);
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* Called by the default implementation of the <code>onBindOnNewForm</code> version
* with all parameters, after standard binding when displaying the form view.
* Only called if <code>bindOnNewForm</code> is set to <code>true</code>.
* <p>The default implementation is empty.
* @param request current render request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(RenderRequest, Object, BindException)
* @see #setBindOnNewForm(boolean)
*/
protected void onBindOnNewForm(RenderRequest request, Object command) throws Exception {
}
/**
* Return the form object for the given request.
* <p>Calls <code>formBackingObject</code> if the object is not in the session
* @param request current request
* @return object form to bind onto
* @see #formBackingObject
*/
protected final Object getCommand(PortletRequest request) throws Exception {
// If not in session-form mode, create a new form-backing object.
if (!isSessionForm()) {
return formBackingObject(request);
}
// Session-form mode: retrieve form object from portlet session attribute.
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Must have session when trying to bind (in session-form mode)");
}
String formAttrName = getFormSessionAttributeName(request);
Object sessionFormObject = session.getAttribute(formAttrName);
if (sessionFormObject == null) {
throw new PortletSessionRequiredException("Form object not found in session (in session-form mode)");
}
// Remove form object from porlet session: we might finish the form workflow
// in this request. If it turns out that we need to show the form view again,
// we'll re-bind the form object to the portlet session.
if (logger.isDebugEnabled()) {
logger.debug("Removing form session attribute [" + formAttrName + "]");
}
session.removeAttribute(formAttrName);
// Check the command object to make sure its valid
if (!checkCommand(sessionFormObject)) {
throw new PortletSessionRequiredException("Object found in session does not match commandClass");
}
return sessionFormObject;
}
/**
* Retrieve a backing object for the current form from the given request.
* <p>The properties of the form object will correspond to the form field values
* in your form view. This object will be exposed in the model under the specified
* command name, to be accessed under that name in the view: for example, with
* a "spring:bind" tag. The default command name is "command".
* <p>Note that you need to activate session form mode to reuse the form-backing
* object across the entire form workflow. Else, a new instance of the command
* class will be created for each submission attempt, just using this backing
* object as template for the initial form.
* <p>The default implementation calls <code>BaseCommandController.createCommand</code>,
* creating a new empty instance of the command class.
* Subclasses can override this to provide a preinitialized backing object.
* @param request current portlet request
* @return the backing object
* @throws Exception in case of invalid state or arguments
* @see #setCommandName
* @see #setCommandClass
* @see #createCommand
*/
protected Object formBackingObject(PortletRequest request) throws Exception {
return createCommand();
}
/**
* Prepare the form model and view, including reference and error data.
* Can show a configured form page, or generate a form view programmatically.
* <p>A typical implementation will call
* <code>showForm(request, errors, "myView")</code>
* to prepare the form view for a specific view name, returning the
* ModelAndView provided there.
* <p>For building a custom ModelAndView, call <code>errors.getModel()</code>
* to populate the ModelAndView model with the command and the Errors instance,
* under the specified command name, as expected by the "spring:bind" tag.
* You also need to include the model returned by <code>referenceData</code>.
* <p>Note: If you decide to have a "formView" property specifying the
* view name, consider using SimpleFormController.
* @param request current render request
* @param response current render response
* @param errors validation errors holder
* @return the prepared form view, or null if handled directly
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
* @see #referenceData(PortletRequest, Object, Errors)
* @see SimpleFormController#setFormView
*/
protected abstract ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors) throws Exception;
/**
* Prepare model and view for the given form, including reference and errors.
* <p>In session form mode: Re-puts the form object in the session when
* returning to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current render request
* @param errors validation errors holder
* @param viewName name of the form view
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String, Map)
* @see #showForm(RenderRequest, RenderResponse, BindException)
*/
protected final ModelAndView showForm(RenderRequest request, BindException errors, String viewName)
throws Exception {
return showForm(request, errors, viewName, null);
}
/**
* Prepare model and view for the given form, including reference and errors,
* adding a controller-specific control model.
* <p>In session form mode: Re-puts the form object in the session when returning
* to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current render request
* @param errors validation errors holder
* @param viewName name of the form view
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String)
* @see #showForm(RenderRequest, RenderResponse, BindException)
*/
protected final ModelAndView showForm(RenderRequest request, BindException errors, String viewName, Map controlModel)
throws Exception {
// In session form mode, re-expose form object as portlet session attribute.
// Re-binding is necessary for proper state handling in a cluster,
// to notify other nodes of changes in the form object.
if (isSessionForm()) {
String formAttrName = getFormSessionAttributeName(request);
if (logger.isDebugEnabled()) {
logger.debug("Setting form session attribute [" + formAttrName + "] to: " + errors.getTarget());
}
request.getPortletSession().setAttribute(formAttrName, errors.getTarget());
}
// Fetch errors model as starting point, containing form object under
// "commandName", and corresponding Errors instance under internal key.
Map model = errors.getModel();
// Merge reference data into model, if any.
Map referenceData = referenceData(request, errors.getTarget(), errors);
if (referenceData != null) {
model.putAll(referenceData);
}
// Merge control attributes into model, if any.
if (controlModel != null) {
model.putAll(controlModel);
}
// Trigger rendering of the specified view, using the final model.
return new ModelAndView(viewName, model);
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current render request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
protected Map referenceData(PortletRequest request, Object command, Errors errors) throws Exception {
return null;
}
/**
* Process render phase of form submission request. Called by <code>handleRequestInternal</code>
* in case of a form submission, with or without binding errors. Implementations
* need to proceed properly, typically showing a form view in case of binding
* errors or rendering the result of a submit action else.
* <p>For a success view, call <code>errors.getModel()</code> to populate the
* ModelAndView model with the command and the Errors instance, under the
* specified command name, as expected by the "spring:bind" tag. For a form view,
* simply return the ModelAndView object privded by <code>showForm</code>.
* @param request current render request
* @param response current render response
* @param command form object with request parameters bound onto it
* @param errors errors holder
* @return the prepared model and view, or null
* @throws Exception in case of errors
* @see #handleRenderRequestInternal
* @see #processFormSubmission
* @see #isFormSubmission
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception;
/**
* Process action phase of form submission request. Called by <code>handleRequestInternal</code>
* in case of a form submission, with or without binding errors. Implementations
* need to proceed properly, typically performing a submit action if there are no binding errors.
* <p>Subclasses can implement this to provide custom submission handling
* like triggering a custom action. They can also provide custom validation
* or proceed with the submission accordingly.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors errors holder (subclass can add errors if it wants to)
* @throws Exception in case of errors
* @see #handleActionRequestInternal
* @see #renderFormSubmission
* @see #isFormSubmission
* @see org.springframework.validation.Errors
*/
protected abstract void processFormSubmission(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception;
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation simply tries to resubmit the form with a new form object.
* This should also work if the user hit the back button, changed some form data,
* and resubmitted the form.
* <p>Note: To avoid duplicate submissions, you need to override this method.
* Either show some "invalid submit" message, or call <code>showNewForm</code> for
* resetting the form (prepopulating it with the current values if "bindOnNewForm"
* is true). In this case, the form object in the session serves as transaction token.
* <pre class="code">
* protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
* return showNewForm(request, response);
* }</pre>
* You can also show a new form but with special errors registered on it:
* <pre class="code">
* protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
* BindException errors = getErrorsForNewForm(request);
* errors.reject("duplicateFormSubmission", "Duplicate form submission");
* return showForm(request, response, errors);
* }</pre>
* <p><b>WARNING:</b> If you override this method, be sure to also override the action
* phase version of this method so that it will not attempt to perform the resubmit
* action by default.
* @param request current render request
* @param response current render response
* @return a prepared view, or null if handled directly
* @throws Exception in case of errors
* @see #handleInvalidSubmit
*/
protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
throws Exception {
return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
}
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation simply tries to resubmit the form with a new form object.
* This should also work if the user hit the back button, changed some form data,
* and resubmitted the form.
* <p>Note: To avoid duplicate submissions, you need to override this method.
* Most likely you will simply want it to do nothing here in the action phase
* and diplay an appropriate error and a new form in the render phase.
* <pre class="code">
* protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
* }</pre>
* <p>If you override this method but you do need a command object and bind errors
* in the render phase, be sure to call {@link #setRenderCommandAndErrors setRenderCommandAndErrors}
* from here.
* @param request current action request
* @param response current action response
* @throws Exception in case of errors
* @see #renderInvalidSubmit
* @see #setRenderCommandAndErrors
*/
protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
passRenderParameters(request, response);
Object command = formBackingObject(request);
if (command == null) {
throw new PortletException("Form object returned by formBackingObject() must not be null");
}
if (!checkCommand(command)) {
throw new PortletException("Form object returned by formBackingObject() must match commandClass");
}
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
processFormSubmission(request, response, command, errors);
setRenderCommandAndErrors(request, command, errors);
}
}

View File

@ -0,0 +1,975 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.util.PortletUtils;
/**
* Form controller for typical wizard-style workflows.
*
* <p>In contrast to classic forms, wizards have more than one form view page.
* Therefore, there are various actions instead of one single submit action:
* <ul>
* <li>finish: trying to leave the wizard successfully, i.e. performing its
* final action, and thus needing a valid state;
* <li>cancel: leaving the wizard without performing its final action, and
* thus without regard to the validity of its current state;
* <li>page change: showing another wizard page, e.g. the next or previous
* one, with regard to "dirty back" and "dirty forward".
* </ul>
*
* <p>Finish and cancel actions can be triggered by request parameters, named
* PARAM_FINISH ("_finish") and PARAM_CANCEL ("_cancel"), ignoring parameter
* values to allow for HTML buttons. The target page for page changes can be
* specified by PARAM_TARGET, appending the page number to the parameter name
* (e.g. "_target1"). The action parameters are recognized when triggered by
* image buttons too (via "_finish.x", "_abort.x", or "_target1.x").
*
* <p>The current page number will be stored in the session. It can also be
* specified as request parameter PARAM_PAGE ("_page") in order to properly handle
* usage of the back button in a browser: In this case, a submission will always
* contain the correct page number, even if the user submitted from an old view.
*
* <p>The page can only be changed if it validates correctly, except if a
* "dirty back" or "dirty forward" is allowed. At finish, all pages get
* validated again to guarantee a consistent state.
*
* <p>Note that a validator's default validate method is not executed when using
* this class! Rather, the <code>validatePage</code> implementation should call
* special <code>validateXXX</code> methods that the validator needs to provide,
* validating certain pieces of the object. These can be combined to validate
* the elements of individual pages.
*
* <p>Note: Page numbering starts with 0, to be able to pass an array
* consisting of the corresponding view names to the "pages" bean property.
*
* <p>Parameters indicated with <code>setPassRenderParameters</code> will be present
* for each page. If there are render parameters you need in <code>renderFinish</code>
* or <code>renderCancel</code>, then you need to pass those forward from the
* <code>processFinish</code> or <code>processCancel</code> methods, respectively.
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @see #setPages
* @see #validatePage
* @see #processFinish
* @see #processCancel
*/
public abstract class AbstractWizardFormController extends AbstractFormController {
/**
* Parameter triggering the finish action.
* Can be called from any wizard page!
*/
public static final String PARAM_FINISH = "_finish";
/**
* Parameter triggering the cancel action.
* Can be called from any wizard page!
*/
public static final String PARAM_CANCEL = "_cancel";
/**
* Parameter specifying the target page,
* appending the page number to the name.
*/
public static final String PARAM_TARGET = "_target";
/**
* Parameter specifying the current page as value. Not necessary on
* form pages, but allows to properly handle usage of the back button.
* @see #setPageAttribute
*/
public static final String PARAM_PAGE = "_page";
private String[] pages;
private String pageAttribute;
private boolean allowDirtyBack = true;
private boolean allowDirtyForward = false;
/**
* Create a new AbstractWizardFormController.
* <p>"sessionForm" is automatically turned on, "validateOnBinding"
* turned off, and "cacheSeconds" set to 0 by the base class
* (-> no caching for all form controllers).
*/
public AbstractWizardFormController() {
// AbstractFormController sets default cache seconds to 0.
super();
// Always needs session to keep data from all pages.
setSessionForm(true);
// Never validate everything on binding ->
// wizards validate individual pages.
setValidateOnBinding(false);
}
/**
* Set the wizard pages, i.e. the view names for the pages.
* The array index is interpreted as page number.
* @param pages view names for the pages
*/
public final void setPages(String[] pages) {
if (pages == null || pages.length == 0) {
throw new IllegalArgumentException("No wizard pages defined");
}
this.pages = pages;
}
/**
* Return the wizard pages, i.e. the view names for the pages.
* The array index corresponds to the page number.
* <p>Note that a concrete wizard form controller might override
* <code>getViewName(PortletRequest, Object, int)</code> to
* determine the view name for each page dynamically.
* @see #getViewName(PortletRequest, Object, int)
*/
public final String[] getPages() {
return this.pages;
}
/**
* Return the number of wizard pages.
* Useful to check whether the last page has been reached.
* <p>Note that a concrete wizard form controller might override
* <code>getPageCount(PortletRequest, Object)</code> to determine
* the page count dynamically.
* @see #getPageCount(PortletRequest, Object)
*/
protected final int getPageCount() {
return this.pages.length;
}
/**
* Set the name of the page attribute in the model, containing
* an Integer with the current page number.
* <p>This will be necessary for single views rendering multiple view pages.
* It also allows for specifying the optional "_page" parameter.
* @param pageAttribute name of the page attribute
* @see #PARAM_PAGE
*/
public final void setPageAttribute(String pageAttribute) {
this.pageAttribute = pageAttribute;
}
/**
* Return the name of the page attribute in the model.
*/
public final String getPageAttribute() {
return this.pageAttribute;
}
/**
* Set if "dirty back" is allowed, i.e. if moving to a former wizard
* page is allowed in case of validation errors for the current page.
* @param allowDirtyBack if "dirty back" is allowed
*/
public final void setAllowDirtyBack(boolean allowDirtyBack) {
this.allowDirtyBack = allowDirtyBack;
}
/**
* Return whether "dirty back" is allowed.
*/
public final boolean isAllowDirtyBack() {
return this.allowDirtyBack;
}
/**
* Set if "dirty forward" is allowed, i.e. if moving to a later wizard
* page is allowed in case of validation errors for the current page.
* @param allowDirtyForward if "dirty forward" is allowed
*/
public final void setAllowDirtyForward(boolean allowDirtyForward) {
this.allowDirtyForward = allowDirtyForward;
}
/**
* Return whether "dirty forward" is allowed.
*/
public final boolean isAllowDirtyForward() {
return this.allowDirtyForward;
}
/**
* Calls page-specific onBindAndValidate method.
*/
protected final void onBindAndValidate(PortletRequest request, Object command, BindException errors)
throws Exception {
onBindAndValidate(request, command, errors, getCurrentPage(request));
}
/**
* Callback for custom post-processing in terms of binding and validation.
* Called on each submit, after standard binding but before page-specific
* validation of this wizard form controller.
* <p>Note: AbstractWizardFormController does not perform standard
* validation on binding but rather applies page-specific validation
* on processing the form submission.
* @param request current portlet request
* @param command bound command
* @param errors Errors instance for additional custom validation
* @param page current wizard page
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #processFormSubmission
* @see org.springframework.validation.Errors
*/
protected void onBindAndValidate(PortletRequest request, Object command, BindException errors, int page)
throws Exception {
}
/**
* Consider an explicit finish or cancel request as a form submission too.
* @see #isFinishRequest(PortletRequest)
* @see #isCancelRequest(PortletRequest)
*/
protected boolean isFormSubmission(PortletRequest request) {
return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
}
/**
* Calls page-specific referenceData method.
*/
protected final Map referenceData(PortletRequest request, Object command, Errors errors)
throws Exception {
return referenceData(request, command, errors, getCurrentPage(request));
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation delegates to {@link #referenceData(PortletRequest, int)}.
* Subclasses can override this to set reference data used in the view.
* @param request current portlet request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param page current wizard page
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see #referenceData(PortletRequest, int)
* @see org.springframework.web.portlet.ModelAndView
*/
protected Map referenceData(PortletRequest request, Object command, Errors errors, int page)
throws Exception {
return referenceData(request, page);
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current portlet request
* @param page current wizard page
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see org.springframework.web.portlet.ModelAndView
*/
protected Map referenceData(PortletRequest request, int page) throws Exception {
return null;
}
/**
* Show the first page as form view.
* <p>This can be overridden in subclasses, e.g. to prepare wizard-specific
* error views in case of an Exception.
*/
protected ModelAndView showForm(
RenderRequest request, RenderResponse response, BindException errors) throws Exception {
return showPage(request, errors, getInitialPage(request, errors.getTarget()));
}
/**
* Prepare the form model and view, including reference and error data,
* for the given page. Can be used in <code>processFinish</code> implementations,
* to show the corresponding page in case of validation errors.
* @param request current portlet render request
* @param errors validation errors holder
* @param page number of page to show
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
*/
protected final ModelAndView showPage(RenderRequest request, BindException errors, int page)
throws Exception {
if (page >= 0 && page < getPageCount(request, errors.getTarget())) {
if (logger.isDebugEnabled()) {
logger.debug("Showing wizard page " + page + " for form bean '" + getCommandName() + "'");
}
// Set page session attribute, expose overriding request attribute.
Integer pageInteger = new Integer(page);
String pageAttrName = getPageSessionAttributeName(request);
if (isSessionForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Setting page session attribute [" + pageAttrName + "] to: " + pageInteger);
}
request.getPortletSession().setAttribute(pageAttrName, pageInteger);
}
request.setAttribute(pageAttrName, pageInteger);
// Set page request attribute for evaluation by views.
Map controlModel = new HashMap();
if (this.pageAttribute != null) {
controlModel.put(this.pageAttribute, new Integer(page));
}
String viewName = getViewName(request, errors.getTarget(), page);
return showForm(request, errors, viewName, controlModel);
}
else {
throw new PortletException("Invalid wizard page number: " + page);
}
}
/**
* Return the page count for this wizard form controller.
* <p>The default implementation delegates to {@link #getPageCount()}.
* Can be overridden to dynamically adapt the page count.
* @param request current portlet request
* @param command the command object as returned by formBackingObject
* @return the current page count
* @see #getPageCount
*/
protected int getPageCount(PortletRequest request, Object command) {
return getPageCount();
}
/**
* Return the name of the view for the specified page of this wizard form controller.
* <p>The default implementation takes the view name from the {@link #getPages()} array.
* Can be overridden to dynamically switch the page view or to return view names
* for dynamically defined pages.
* @param request current portlet request
* @param command the command object as returned by <code>formBackingObject</code>
* @return the current page count
* @see #getPageCount
*/
protected String getViewName(PortletRequest request, Object command, int page) {
return getPages()[page];
}
/**
* Return the initial page of the wizard, i.e. the page shown at wizard startup.
* <p>The default implementation delegates to {@link #getInitialPage(PortletRequest)}.
* @param request current portlet request
* @param command the command object as returned by <code>formBackingObject</code>
* @return the initial page number
* @see #getInitialPage(PortletRequest)
* @see #formBackingObject
*/
protected int getInitialPage(PortletRequest request, Object command) {
return getInitialPage(request);
}
/**
* Return the initial page of the wizard, i.e. the page shown at wizard startup.
* <p>The default implementation returns 0 for first page.
* @param request current portlet request
* @return the initial page number
*/
protected int getInitialPage(PortletRequest request) {
return 0;
}
/**
* Return the name of the PortletSession attribute that holds the page object
* for this wizard form controller.
* <p>The default implementation delegates to the <code>getPageSessionAttributeName</code>
* version without arguments.
* @param request current portlet request
* @return the name of the form session attribute, or null if not in session form mode
* @see #getPageSessionAttributeName
* @see #getFormSessionAttributeName
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getPageSessionAttributeName(PortletRequest request) {
return getPageSessionAttributeName();
}
/**
* Return the name of the PortletSession attribute that holds the page object
* for this wizard form controller.
* <p>Default is an internal name, of no relevance to applications, as the form
* session attribute is not usually accessed directly. Can be overridden to use
* an application-specific attribute name, which allows other code to access
* the session attribute directly.
* @return the name of the page session attribute
* @see #getFormSessionAttributeName
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getPageSessionAttributeName() {
return getClass().getName() + ".PAGE." + getCommandName();
}
/**
* Pass the page number to the render phase by setting a render parameter.
* This method may not be called when the action calls
* {@link javax.portlet.ActionResponse#sendRedirect(String)}.
* @param response the current action response
* @param page the page number
* @see ActionResponse#setRenderParameter
*/
protected void setPageRenderParameter(ActionResponse response, int page) {
if (logger.isDebugEnabled())
logger.debug("Setting page number render parameter [" + PARAM_PAGE + "] to [" + page + "]");
try {
response.setRenderParameter(PARAM_PAGE, new Integer(page).toString());
}
catch (IllegalStateException ex) {
// ignore in case sendRedirect was already set
}
}
/**
* Pass the the parameter that indicates the target page of the request
* forward to the render phase. If the <code>getTargetPage<code> method
* was overridden, this may need to be overriden as well.
* @param request the current action request
* @param response the current action response
* @see #PARAM_TARGET
* @see #getTargetPage(PortletRequest, int)
* @see #getTargetPage(PortletRequest, Object, Errors, int)
* @see ActionResponse#setRenderParameter
*/
protected void setTargetRenderParameter(ActionRequest request, ActionResponse response) {
try {
Iterator it = PortletUtils.getParametersStartingWith(request, PARAM_TARGET).entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String param = PARAM_TARGET + (String) entry.getKey();
Object value = entry.getValue();
if (logger.isDebugEnabled()) {
logger.debug("Setting target render parameter [" + param + "]");
}
if (value instanceof String) {
response.setRenderParameter(param, (String) value);
}
else if (value instanceof String[]) {
response.setRenderParameter(param, (String[]) value);
}
}
}
catch (IllegalStateException ex) {
// ignore in case sendRedirect was already set
}
}
/**
* Pass the the parameter that indicates a finish request forward to the
* render phase. If the <code>isFinishRequest</code> method
* was overridden, this may need to be overriden as well.
* @param request the current action request
* @param response the current action response
* @see #PARAM_FINISH
* @see #isFinishRequest
* @see ActionResponse#setRenderParameter
*/
protected void setFinishRenderParameter(ActionRequest request, ActionResponse response) {
if (logger.isDebugEnabled())
logger.debug("Setting cancel render parameter [" + PARAM_FINISH + "]");
try {
String name = PortletUtils.getSubmitParameter(request, PARAM_FINISH);
if (name != null)
response.setRenderParameter(name, request.getParameter(name));
}
catch (IllegalStateException ex) {
// ignore in case sendRedirect was already set
}
}
/**
* Pass the the parameter that indicates a cancel request forward to the
* render phase. If the <code>isCancelRequest</code> method
* was overridden, this may need to be overriden as well.
* @param request the current action request
* @param response the current action response
* @see #PARAM_CANCEL
* @see #isCancelRequest
* @see ActionResponse#setRenderParameter
*/
protected void setCancelRenderParameter(ActionRequest request, ActionResponse response) {
if (logger.isDebugEnabled())
logger.debug("Setting cancel render parameter [" + PARAM_CANCEL + "]");
try {
String name = PortletUtils.getSubmitParameter(request, PARAM_CANCEL);
if (name != null)
response.setRenderParameter(name, request.getParameter(name));
}
catch (IllegalStateException ex) {
// ignore in case sendRedirect was already set
}
}
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation for wizard form controllers simply shows the initial page
* of a new wizard form. If you want to show some "invalid submit" message, you need
* to override this method.
* @param request current portlet render request
* @param response current portlet render response
* @return a prepared view, or null if handled directly
* @throws Exception in case of errors
* @see #showNewForm
* @see #setBindOnNewForm
* @see #handleInvalidSubmit
*/
protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
throws Exception {
return showNewForm(request, response);
}
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation for wizard form controllers simply shows the initial page
* of a new wizard form, so here in the action phase this method does nothing. If you
* want to take some action on an invalid submit, you need to override this method.
* @param request current portlet action request
* @param response current portlet action response
* @throws Exception in case of errors
* @see #renderInvalidSubmit
*/
protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
}
/**
* Apply wizard workflow: finish, cancel, page change.
* @see #processFormSubmission
*/
protected final ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
int currentPage = getCurrentPage(request);
String pageAttrName = getPageSessionAttributeName(request);
request.setAttribute(pageAttrName, new Integer(currentPage));
// cancel?
if (isCancelRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
}
return renderCancel(request, response, command, errors);
}
// finish?
if (isFinishRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
}
return renderValidatePagesAndFinish(request, response, command, errors, currentPage);
}
// Normal submit: show specified target page.
int targetPage = getTargetPage(request, command, errors, currentPage);
if (logger.isDebugEnabled()) {
logger.debug("Target page " + targetPage + " requested");
}
if (targetPage != currentPage) {
if (!errors.hasErrors() || (this.allowDirtyBack && targetPage < currentPage) ||
(this.allowDirtyForward && targetPage > currentPage)) {
// Allowed to go to target page.
return showPage(request, errors, targetPage);
}
}
// Show current page again
return showPage(request, errors, currentPage);
}
/**
* Apply wizard workflow: finish, cancel, page change.
* @see #renderFormSubmission
*/
protected final void processFormSubmission(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
int currentPage = getCurrentPage(request);
// Remove page session attribute, provide copy as request attribute.
String pageAttrName = getPageSessionAttributeName(request);
if (isSessionForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Removing page session attribute [" + pageAttrName + "]");
}
request.getPortletSession().removeAttribute(pageAttrName);
}
request.setAttribute(pageAttrName, new Integer(currentPage));
// cancel?
if (isCancelRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
}
setPageRenderParameter(response, currentPage);
setCancelRenderParameter(request, response);
processCancel(request, response, command, errors);
return;
}
// finish?
if (isFinishRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
}
if (!isRedirectAction()) {
setPageRenderParameter(response, currentPage);
setFinishRenderParameter(request, response);
}
validatePagesAndFinish(request, response, command, errors, currentPage);
return;
}
// Normal submit: validate current page
if (!suppressValidation(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Validating wizard page " + currentPage + " for form bean '" + getCommandName() + "'");
}
validatePage(command, errors, currentPage, false);
}
setPageRenderParameter(response, currentPage);
setTargetRenderParameter(request, response);
passRenderParameters(request, response);
// Give subclasses a change to perform custom post-procession
// of the current page and its command object.
postProcessPage(request, command, errors, currentPage);
}
/**
* Return the current page number. Used by {@link #processFormSubmission}.
* <p>The default implementation checks the page session attribute.
* Subclasses can override this for customized page determination.
* @param request current portlet request
* @return the current page number
* @see #getPageSessionAttributeName()
*/
protected int getCurrentPage(PortletRequest request) {
// Check for overriding attribute in request.
String pageAttrName = getPageSessionAttributeName(request);
Integer pageAttr = (Integer) request.getAttribute(pageAttrName);
if (pageAttr != null) {
return pageAttr.intValue();
}
// Check for explicit request parameter.
String pageParam = request.getParameter(PARAM_PAGE);
if (pageParam != null) {
return Integer.parseInt(pageParam);
}
// Check for original attribute in session.
if (isSessionForm()) {
pageAttr = (Integer) request.getPortletSession().getAttribute(pageAttrName);
if (pageAttr != null) {
return pageAttr.intValue();
}
}
throw new IllegalStateException("Page attribute [" + pageAttrName + "] neither found in session nor in request");
}
/**
* Determine whether the incoming request is a request to finish the
* processing of the current form.
* <p>By default, this method returns <code>true</code> if a parameter
* matching the "_finish" key is present in the request, otherwise it
* returns <code>false</code>. Subclasses may override this method
* to provide custom logic to detect a finish request.
* <p>The parameter is recognized both when sent as a plain parameter
* ("_finish") or when triggered by an image button ("_finish.x").
* @param request current portlet request
* @return whether the request indicates to finish form processing
* @see #PARAM_FINISH
*/
protected boolean isFinishRequest(PortletRequest request) {
return PortletUtils.hasSubmitParameter(request, PARAM_FINISH);
}
/**
* Determine whether the incoming request is a request to cancel the
* processing of the current form.
* <p>By default, this method returns <code>true</code> if a parameter
* matching the "_cancel" key is present in the request, otherwise it
* returns <code>false</code>. Subclasses may override this method
* to provide custom logic to detect a cancel request.
* <p>The parameter is recognized both when sent as a plain parameter
* ("_cancel") or when triggered by an image button ("_cancel.x").
* @param request current portlet request
* @return whether the request indicates to cancel form processing
* @see #PARAM_CANCEL
*/
protected boolean isCancelRequest(PortletRequest request) {
return PortletUtils.hasSubmitParameter(request, PARAM_CANCEL);
}
/**
* Return the target page specified in the request.
* <p>The default implementation delegates to {@link #getTargetPage(PortletRequest, int)}.
* Subclasses can override this for customized target page determination.
* @param request current portlet request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
* @see #getTargetPage(PortletRequest, int)
*/
protected int getTargetPage(PortletRequest request, Object command, Errors errors, int currentPage) {
return getTargetPage(request, currentPage);
}
/**
* Return the target page specified in the request.
* <p>The default implementation examines "_target" parameter (e.g. "_target1").
* Subclasses can override this for customized target page determination.
* @param request current portlet request
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
* @see #PARAM_TARGET
*/
protected int getTargetPage(PortletRequest request, int currentPage) {
return PortletUtils.getTargetPage(request, PARAM_TARGET, currentPage);
}
/**
* Validate all pages and process finish.
* If there are page validation errors, show the corresponding view page.
* @see #validatePagesAndFinish
*/
private ModelAndView renderValidatePagesAndFinish(
RenderRequest request, RenderResponse response, Object command, BindException errors, int currentPage)
throws Exception {
// In case of any errors -> show current page.
if (errors.hasErrors())
return showPage(request, errors, currentPage);
// No remaining errors -> proceed with finish.
return renderFinish(request, response, command, errors);
}
/**
* Validate all pages and process finish.
* If there are page validation errors, show the corresponding view page.
* @see #renderValidatePagesAndFinish
*/
private void validatePagesAndFinish(
ActionRequest request, ActionResponse response, Object command, BindException errors, int currentPage)
throws Exception {
// In case of binding errors -> show current page.
if (errors.hasErrors()) {
setPageRenderParameter(response, currentPage);
passRenderParameters(request, response);
return;
}
if (!suppressValidation(request)) {
// In case of remaining errors on a page -> show the page.
for (int page = 0; page < getPageCount(request, command); page++) {
validatePage(command, errors, page, true);
if (errors.hasErrors()) {
setPageRenderParameter(response, currentPage);
passRenderParameters(request, response);
return;
}
}
}
// No remaining errors -> proceed with finish.
if (!isRedirectAction())
setPageRenderParameter(response, currentPage);
processFinish(request, response, command, errors);
}
/**
* Template method for custom validation logic for individual pages.
* The default implementation calls <code>validatePage(command, errors, page)</code>.
* <p>Implementations will typically call fine-granular <code>validateXXX</code>
* methods of this instance's Validator, combining them to validation of the
* corresponding pages. The Validator's default <code>validate</code> method
* will not be called by a wizard form controller!
* @param command form object with the current wizard state
* @param errors validation errors holder
* @param page number of page to validate
* @param finish whether this method is called during final revalidation on finish
* (else, it is called for validating the current page)
* @see #validatePage(Object, Errors, int)
* @see org.springframework.validation.Validator#validate
*/
protected void validatePage(Object command, Errors errors, int page, boolean finish) {
validatePage(command, errors, page);
}
/**
* Template method for custom validation logic for individual pages.
* The default implementation is empty.
* <p>Implementations will typically call fine-granular validateXXX methods of this
* instance's validator, combining them to validation of the corresponding pages.
* The validator's default <code>validate</code> method will not be called by a
* wizard form controller!
* @param command form object with the current wizard state
* @param errors validation errors holder
* @param page number of page to validate
* @see org.springframework.validation.Validator#validate
*/
protected void validatePage(Object command, Errors errors, int page) {
}
/**
* Post-process the given page after binding and validation, potentially
* updating its command object. The passed-in request might contain special
* parameters sent by the page.
* <p>Only invoked when displaying another page or the same page again,
* not when finishing or cancelling.
* @param request current action request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param page number of page to post-process
* @throws Exception in case of invalid state or arguments
*/
protected void postProcessPage(ActionRequest request, Object command, Errors errors, int page)
throws Exception {
}
/**
* Template method for the render phase of the finish action of this wizard.
* <p>The default implementation throws a PortletException, saying that a finish
* render request is not supported by this controller. Thus, you do not need to
* implement this template method if you do not need to render after a finish.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current portlet render request
* @param response current portlet render response
* @param command form object with the current wizard state
* @param errors validation errors holder
* @return the finish view
* @throws Exception in case of invalid state or arguments
* @see #processFinish
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView renderFinish(
RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
throw new PortletException("Wizard form controller class [" + getClass().getName() + "] does not support a finish render request");
}
/**
* Template method for the action phase of the finish action of this wizard.
* <p>The default implementation throws a PortletException, saying that a finish
* action request is not supported by this controller. You will almost certainly
* need to override this method.
* @param request current portlet action request
* @param response current portlet action response
* @param command form object with the current wizard state
* @param errors validation errors holder
* @throws Exception in case of invalid state or arguments
* @see #renderFinish
* @see org.springframework.validation.Errors
*/
protected void processFinish(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
throw new PortletException(
"Wizard form controller class [" + getClass().getName() + "] does not support a finish action request");
}
/**
* Template method for the render phase of the cancel action of this wizard.
* <p>The default implementation throws a PortletException, saying that a cancel
* render request is not supported by this controller. Thus, you do not need to
* implement this template method if you do not support a cancel operation.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current portlet render request
* @param response current portlet render response
* @param command form object with the current wizard state
* @param errors Errors instance containing errors
* @return the cancellation view
* @throws Exception in case of invalid state or arguments
* @see #processCancel
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView renderCancel(
RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
throw new PortletException(
"Wizard form controller class [" + getClass().getName() + "] does not support a cancel render request");
}
/**
* Template method for the action phase of the cancel action of this wizard.
* <p>The default implementation throws a PortletException, saying that a cancel
* action request is not supported by this controller. Thus, you do not need to
* implement this template method if you do not support a cancel operation.
* @param request current portlet action request
* @param response current portlet action response
* @param command form object with the current wizard state
* @param errors Errors instance containing errors
* @throws Exception in case of invalid state or arguments
* @see #renderCancel
* @see org.springframework.validation.Errors
*/
protected void processCancel(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
throw new PortletException(
"Wizard form controller class [" + getClass().getName() + "] does not support a cancel action request");
}
}

View File

@ -0,0 +1,663 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.context.PortletWebRequest;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
/**
* <p>Controller implementation which creates an object (the command object) on
* receipt of a request and attempts to populate this object with request parameters.</p>
*
* <p>This controller is the base for all controllers wishing to populate
* JavaBeans based on request parameters, validate the content of such
* JavaBeans using {@link Validator Validators} and use custom editors (in the form of
* {@link java.beans.PropertyEditor PropertyEditors}) to transform
* objects into strings and vice versa, for example. Three notions are mentioned here:</p>
*
* <p><b>Command class:</b><br>
* An instance of the command class will be created for each request and populated
* with request parameters. A command class can basically be any Java class; the only
* requirement is a no-arg constructor. The command class should preferably be a
* JavaBean in order to be able to populate bean properties with request parameters.</p>
*
* <p><b>Populating using request parameters and PropertyEditors:</b><br>
* Upon receiving a request, any BaseCommandController will attempt to fill the
* command object using the request parameters. This is done using the typical
* and well-known JavaBeans property notation. When a request parameter named
* <code>'firstName'</code> exists, the framework will attempt to call
* <code>setFirstName([value])</code> passing the value of the parameter. Nested properties
* are of course supported. For instance a parameter named <code>'address.city'</code>
* will result in a <code>getAddress().setCity([value])</code> call on the
* command class.</p>
*
* <p>It's important to realize that you are not limited to String arguments in
* your JavaBeans. Using the PropertyEditor-notion as supplied by the
* java.beans package, you will be able to transform Strings to Objects and
* the other way around. For instance <code>setLocale(Locale loc)</code> is
* perfectly possible for a request parameter named <code>locale</code> having
* a value of <code>en</code>, as long as you register the appropriate
* PropertyEditor in the Controller (see {@link #initBinder initBinder()}
* for more information on that matter).</p>
*
* <p><b>Validators:</b>
* After the controller has successfully populated the command object with
* parameters from the request, it will use any configured validators to
* validate the object. Validation results will be put in a
* {@link org.springframework.validation.Errors Errors} object which can be
* used in a View to render any input problems.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* Since this class is an abstract base class for more specific implementation,
* it does not override the <code>handleRequestInternal()</code> methods and also has no
* actual workflow. Implementing classes like
* {@link AbstractFormController AbstractFormController},
* {@link AbstractCommandController AbstractCommandController},
* {@link SimpleFormController SimpleFormController} and
* {@link AbstractWizardFormController AbstractWizardFormController}
* provide actual functionality and workflow.
* More information on workflow performed by superclasses can be found
* <a href="AbstractController.html#workflow">here</a>.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></th>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>commandName</td>
* <td>command</td>
* <td>the name to use when binding the instantiated command class
* to the request</td>
* </tr>
* <tr>
* <td>commandClass</td>
* <td><i>null</i></td>
* <td>the class to use upon receiving a request and which to fill
* using the request parameters. What object is used and whether
* or not it should be created is defined by extending classes
* and their configuration properties and methods.</td>
* </tr>
* <tr>
* <td>validators</td>
* <td><i>null</i></td>
* <td>Array of Validator beans. The validator will be called at appropriate
* places in the workflow of subclasses (have a look at those for more info)
* to validate the command object.</td>
* </tr>
* <tr>
* <td>validator</td>
* <td><i>null</i></td>
* <td>Short-form property for setting only one Validator bean (usually passed in
* using a &lt;ref bean="beanId"/&gt; property.</td>
* </tr>
* <tr>
* <td>validateOnBinding</td>
* <td>true</td>
* <td>Indicates whether or not to validate the command object after the
* object has been populated with request parameters.</td>
* </tr>
* </table>
* </p>
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public abstract class BaseCommandController extends AbstractController {
/**
* Unlike the servlet version of these classes, we have to deal with the
* two-phase nature of the portlet request. To do this, we need to pass
* forward the command object and the bind/validation errors that occured
* on the command object from the action phase to the render phase.
* The only direct way to pass things forward and preserve them for each
* render request is through render parameters, but these are limited to
* String objects and we need to pass more complicated objects. The only
* other way to do this is in the session. The bad thing about using the
* session is that we have no way of knowing when we are done re-rendering
* the request and so we don't know when we can remove the objects from
* the session. So we will end up polluting the session with old objects
* when we finally leave the render of this controller and move on to
* somthing else. To minimize the pollution, we will use a static string
* value as the session attribute name. At least this way we are only ever
* leaving one orphaned set behind. The methods that return these names
* can be overridden if you want to use a different method, but be aware
* of the session pollution that may occur.
*/
private static final String RENDER_COMMAND_SESSION_ATTRIBUTE =
"org.springframework.web.portlet.mvc.RenderCommand";
private static final String RENDER_ERRORS_SESSION_ATTRIBUTE =
"org.springframework.web.portlet.mvc.RenderErrors";
public static final String DEFAULT_COMMAND_NAME = "command";
private String commandName = DEFAULT_COMMAND_NAME;
private Class commandClass;
private Validator[] validators;
private boolean validateOnBinding = true;
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor;
private PropertyEditorRegistrar[] propertyEditorRegistrars;
private WebBindingInitializer webBindingInitializer;
/**
* Set the name of the command in the model.
* The command object will be included in the model under this name.
*/
public final void setCommandName(String commandName) {
this.commandName = commandName;
}
/**
* Return the name of the command in the model.
*/
public final String getCommandName() {
return this.commandName;
}
/**
* Set the command class for this controller.
* An instance of this class gets populated and validated on each request.
*/
public final void setCommandClass(Class commandClass) {
this.commandClass = commandClass;
}
/**
* Return the command class for this controller.
*/
public final Class getCommandClass() {
return this.commandClass;
}
/**
* Set the primary Validator for this controller. The Validator
* must support the specified command class. If there are one
* or more existing validators set already when this method is
* called, only the specified validator will be kept. Use
* {@link #setValidators(Validator[])} to set multiple validators.
*/
public final void setValidator(Validator validator) {
this.validators = new Validator[] {validator};
}
/**
* @return the primary Validator for this controller.
*/
public final Validator getValidator() {
return (this.validators != null && this.validators.length > 0 ? this.validators[0] : null);
}
/**
* Set the Validators for this controller.
* The Validator must support the specified command class.
*/
public final void setValidators(Validator[] validators) {
this.validators = validators;
}
/**
* Return the Validators for this controller.
*/
public final Validator[] getValidators() {
return this.validators;
}
/**
* Set if the Validator should get applied when binding.
*/
public final void setValidateOnBinding(boolean validateOnBinding) {
this.validateOnBinding = validateOnBinding;
}
/**
* Return if the Validator should get applied when binding.
*/
public final boolean isValidateOnBinding() {
return this.validateOnBinding;
}
/**
* Set the strategy to use for resolving errors into message codes.
* Applies the given strategy to all data binders used by this controller.
* <p>Default is <code>null</code>, i.e. using the default strategy of the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setMessageCodesResolver
*/
public final void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
this.messageCodesResolver = messageCodesResolver;
}
/**
* Return the strategy to use for resolving errors into message codes (if any).
*/
public final MessageCodesResolver getMessageCodesResolver() {
return this.messageCodesResolver;
}
/**
* Set the strategy to use for processing binding errors, that is,
* required field errors and <code>PropertyAccessException</code>s.
* <p>Default is <code>null</code>, i.e. using the default strategy of
* the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setBindingErrorProcessor
*/
public final void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
this.bindingErrorProcessor = bindingErrorProcessor;
}
/**
* Return the strategy to use for processing binding errors (if any).
*/
public final BindingErrorProcessor getBindingErrorProcessor() {
return this.bindingErrorProcessor;
}
/**
* Specify a single PropertyEditorRegistrar to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as an alternative to <code>initBinder</code>.
* @see #initBinder
*/
public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
}
/**
* Specify one or more PropertyEditorRegistrars to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as alternative to <code>initBinder</code>.
* @see #initBinder
*/
public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
/**
* Return the PropertyEditorRegistrars (if any) to be applied
* to every DataBinder that this controller uses.
*/
public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
return this.propertyEditorRegistrars;
}
/**
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
* <p>Allows for factoring out the entire binder configuration
* to separate objects, as an alternative to {@link #initBinder}.
*/
public final void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Return the WebBindingInitializer (if any) which will apply pre-configured
* configuration to every DataBinder that this controller uses.
*/
public final WebBindingInitializer getWebBindingInitializer() {
return this.webBindingInitializer;
}
protected void initApplicationContext() {
if (this.validators != null) {
for (int i = 0; i < this.validators.length; i++) {
if (this.commandClass != null && !this.validators[i].supports(this.commandClass))
throw new IllegalArgumentException("Validator [" + this.validators[i] +
"] does not support command class [" +
this.commandClass.getName() + "]");
}
}
}
/**
* Retrieve a command object for the given request.
* <p>The default implementation calls {@link #createCommand()}.
* Subclasses can override this.
* @param request current portlet request
* @return object command to bind onto
* @see #createCommand
*/
protected Object getCommand(PortletRequest request) throws Exception {
return createCommand();
}
/**
* Create a new command instance for the command class of this controller.
* <p>This implementation uses <code>BeanUtils.instantiateClass</code>,
* so the command needs to have a no-arg constructor (supposed to be
* public, but not required to).
* @return the new command instance
* @throws Exception if the command object could not be instantiated
* @see org.springframework.beans.BeanUtils#instantiateClass(Class)
*/
protected final Object createCommand() throws Exception {
if (this.commandClass == null) {
throw new IllegalStateException("Cannot create command without commandClass being set - " +
"either set commandClass or (in a form controller) override formBackingObject");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating new command of class [" + this.commandClass.getName() + "]");
}
return BeanUtils.instantiateClass(this.commandClass);
}
/**
* Check if the given command object is a valid for this controller,
* i.e. its command class.
* @param command the command object to check
* @return if the command object is valid for this controller
*/
protected final boolean checkCommand(Object command) {
return (this.commandClass == null || this.commandClass.isInstance(command));
}
/**
* Bind the parameters of the given request to the given command object.
* @param request current portlet request
* @param command the command to bind onto
* @return the PortletRequestDataBinder instance for additional custom validation
* @throws Exception in case of invalid state or arguments
*/
protected final PortletRequestDataBinder bindAndValidate(PortletRequest request, Object command)
throws Exception {
PortletRequestDataBinder binder = createBinder(request, command);
if (!suppressBinding(request)) {
binder.bind(request);
BindException errors = new BindException(binder.getBindingResult());
onBind(request, command, errors);
if (this.validators != null && isValidateOnBinding() && !suppressValidation(request)) {
for (int i = 0; i < this.validators.length; i++) {
ValidationUtils.invokeValidator(this.validators[i], command, errors);
}
}
onBindAndValidate(request, command, errors);
}
return binder;
}
/**
* Return whether to suppress binding for the given request.
* <p>The default implementation always returns <code>false</code>.
* Can be overridden in subclasses to suppress validation:
* for example, if a special request parameter is set.
* @param request current portlet request
* @return whether to suppress binding for the given request
* @see #suppressValidation
*/
protected boolean suppressBinding(PortletRequest request) {
return false;
}
/**
* Create a new binder instance for the given command and request.
* <p>Called by <code>bindAndValidate</code>. Can be overridden to plug in
* custom PortletRequestDataBinder instances.
* <p>The default implementation creates a standard PortletRequestDataBinder and
* invokes <code>prepareBinder</code> and <code>initBinder</code>.
* <p>Note that neither <code>prepareBinder</code> nor <code>initBinder</code>
* will be invoked automatically if you override this method! Call those methods
* at appropriate points of your overridden method.
* @param request current portlet request
* @param command the command to bind onto
* @return the new binder instance
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #prepareBinder
* @see #initBinder
*/
protected PortletRequestDataBinder createBinder(PortletRequest request, Object command)
throws Exception {
PortletRequestDataBinder binder = new PortletRequestDataBinder(command, getCommandName());
prepareBinder(binder);
initBinder(request, binder);
return binder;
}
/**
* Prepare the given binder, applying the specified MessageCodesResolver,
* BindingErrorProcessor and PropertyEditorRegistrars (if any).
* Called by <code>createBinder</code>.
* @param binder the new binder instance
* @see #createBinder
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
*/
protected final void prepareBinder(PortletRequestDataBinder binder) {
if (useDirectFieldAccess()) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.propertyEditorRegistrars != null) {
for (int i = 0; i < this.propertyEditorRegistrars.length; i++) {
this.propertyEditorRegistrars[i].registerCustomEditors(binder);
}
}
}
/**
* Determine whether to use direct field access instead of bean property access.
* Applied by <code>prepareBinder</code>.
* <p>The default is <code>false</code>. Can be overridden in subclasses.
* @see #prepareBinder
* @see org.springframework.validation.DataBinder#initDirectFieldAccess()
*/
protected boolean useDirectFieldAccess() {
return false;
}
/**
* Initialize the given binder instance, for example with custom editors.
* Called by <code>createBinder</code>.
* <p>This method allows you to register custom editors for certain fields of your
* command class. For instance, you will be able to transform Date objects into a
* String pattern and back, in order to allow your JavaBeans to have Date properties
* and still be able to set and display them in an HTML interface.
* <p>The default implementation is empty.
* @param request current portlet request
* @param binder new binder instance
* @throws Exception in case of invalid state or arguments
* @see #createBinder
* @see org.springframework.validation.DataBinder#registerCustomEditor
* @see org.springframework.beans.propertyeditors.CustomDateEditor
*/
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) throws Exception {
if (this.webBindingInitializer != null) {
this.webBindingInitializer.initBinder(binder, new PortletWebRequest(request));
}
}
/**
* Callback for custom post-processing in terms of binding.
* Called on each submit, after standard binding but before validation.
* <p>The default implementation delegates to <code>onBind(request, command)</code>.
* @param request current portlet request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #onBind(PortletRequest, Object)
*/
protected void onBind(PortletRequest request, Object command, BindException errors) throws Exception {
onBind(request, command);
}
/**
* Callback for custom post-processing in terms of binding.
* Called by the default implementation of the <code>onBind</code> version with
* all parameters, after standard binding but before validation.
* <p>The default implementation is empty.
* @param request current portlet request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBind(PortletRequest, Object, BindException)
*/
protected void onBind(PortletRequest request, Object command) throws Exception {
}
/**
* Return whether to suppress validation for the given request.
* <p>The default implementation always returns <code>false</code>.
* Can be overridden in subclasses to suppress validation:
* for example, if a special request parameter is set.
* @param request current portlet request
* @return whether to suppress validation for the given request
*/
protected boolean suppressValidation(PortletRequest request) {
return false;
}
/**
* Callback for custom post-processing in terms of binding and validation.
* Called on each submit, after standard binding and validation,
* but before error evaluation.
* <p>The default implementation is empty.
* @param request current portlet request
* @param command the command object, still allowing for further binding
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see org.springframework.validation.Errors
*/
protected void onBindAndValidate(PortletRequest request, Object command, BindException errors)
throws Exception {
}
/**
* Return the name of the session attribute that holds
* the render phase command object for this form controller.
* @return the name of the render phase command object session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getRenderCommandSessionAttributeName() {
return RENDER_COMMAND_SESSION_ATTRIBUTE;
}
/**
* Return the name of the session attribute that holds
* the render phase command object for this form controller.
* @return the name of the render phase command object session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getRenderErrorsSessionAttributeName() {
return RENDER_ERRORS_SESSION_ATTRIBUTE;
}
/**
* Get the command object cached for the render phase.
* @see #getRenderErrors
* @see #getRenderCommandSessionAttributeName
* @see #setRenderCommandAndErrors
*/
protected final Object getRenderCommand(RenderRequest request) throws PortletException {
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Could not obtain portlet session");
}
Object command = session.getAttribute(getRenderCommandSessionAttributeName());
if (command == null) {
throw new PortletSessionRequiredException("Could not obtain command object from portlet session");
}
return command;
}
/**
* Get the bind and validation errors cached for the render phase.
* @see #getRenderCommand
* @see #getRenderErrorsSessionAttributeName
* @see #setRenderCommandAndErrors
*/
protected final BindException getRenderErrors(RenderRequest request) throws PortletException {
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Could not obtain portlet session");
}
BindException errors = (BindException) session.getAttribute(getRenderErrorsSessionAttributeName());
if (errors == null) {
throw new PortletSessionRequiredException("Could not obtain errors object from portlet session");
}
return errors;
}
/**
* Set the command object and errors object for the render phase.
* @param request the current action request
* @param command the command object to preserve for the render phase
* @param errors the errors from binding and validation to preserve for the render phase
* @see #getRenderCommand
* @see #getRenderErrors
* @see #getRenderCommandSessionAttributeName
* @see #getRenderErrorsSessionAttributeName
*/
protected final void setRenderCommandAndErrors(
ActionRequest request, Object command, BindException errors) throws Exception {
logger.debug("Storing command and error objects in session for render phase");
PortletSession session = request.getPortletSession();
session.setAttribute(getRenderCommandSessionAttributeName(), command);
session.setAttribute(getRenderErrorsSessionAttributeName(), errors);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.ModelAndView;
/**
* Base portlet Controller interface, representing a component that receives
* RenderRequest/RenderResponse and ActionRequest/ActionResponse like a
* <code>Portlet</code> but is able to participate in an MVC workflow.
*
* <p>Any implementation of the portlet Controller interface should be a
* <i>reusable, threadsafe</i> class, capable of handling multiple
* portlet requests throughout the lifecycle of an application. To be able to
* configure Controller(s) in an easy way, Controllers are usually JavaBeans.</p>
*
* <p><b><a name="workflow">Workflow</a>:</b></p>
*
* <p>After the DispatcherPortlet has received a request and has done its work
* to resolve locales, themes and suchlike, it tries to resolve a
* Controller to handle that request, using a
* {@link org.springframework.web.portlet.HandlerMapping HandlerMapping}.
* When a Controller has been found, the
* {@link #handleRenderRequest handleRenderRequest} or {@link #handleActionRequest handleActionRequest}
* method will be invoked, which is responsible for handling the actual
* request and - if applicable - returning an appropriate ModelAndView.
* So actually, these method are the main entrypoint for the
* {@link org.springframework.web.portlet.DispatcherPortlet DispatcherPortlet}
* which delegates requests to controllers. These method - and also this interface -
* should preferrably not be implemented by custom controllers <i>directly</i>, since
* abstract controllers also provided by this package already provide a lot of
* functionality for typical use cases in portlet applications. A few examples of
* those controllers:
* {@link AbstractController AbstractController},
* {@link AbstractCommandController AbstractCommandController},
* {@link AbstractFormController AbstractFormController},
* {@link SimpleFormController SimpleFormController}.</p>
*
* <p>So basically any <i>direct</i> implementation of the Controller interface
* just handles RenderRequests/ActionRequests and should return a ModelAndView, to be
* further used by the DispatcherPortlet. Any additional functionality such as
* optional validation, form handling, etc should be obtained through extending
* one of the abstract controller classes mentioned above.</p>
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @since 2.0
* @see SimpleControllerHandlerAdapter
* @see AbstractController
* @see AbstractCommandController
* @see AbstractFormController
* @see SimpleFormController
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.context.ResourceLoaderAware
* @see org.springframework.web.portlet.context.PortletContextAware
*/
public interface Controller {
/**
* Process the action request. There is nothing to return.
* @param request current portlet action request
* @param response current portlet action response
* @throws Exception in case of errors
*/
void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception;
/**
* Process the render request and return a ModelAndView object which the DispatcherPortlet
* will render. A <code>null</code> return value is not an error: It indicates that this
* object completed request processing itself, thus there is no ModelAndView to render.
* @param request current portlet render request
* @param response current portlet render response
* @return a ModelAndView to render, or null if handled directly
* @throws Exception in case of errors
*/
ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response) throws Exception;
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.ModelAndView;
/**
* <p>Trivial controller that always returns a named view. The view
* can be configured using an exposed configuration property. This
* controller offers an alternative to sending a request straight to a view
* such as a JSP. The advantage here is that the client is not exposed to
* the concrete view technology but rather just to the controller URL;
* the concrete view will be determined by the ViewResolver.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li>Render request is received by the controller</li>
* <li>call to {@link #handleRenderRequestInternal handleRenderRequestInternal} which
* just returns the view, named by the configuration property
* <code>viewName</code>. Nothing more, nothing less</li>
* </ol>
* </p>
*
* <p>This controller does not handle action requests.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>viewName</td>
* <td><i>null</i></td>
* <td>the name of the view the viewResolver will use to forward to
* (if this property is not set, an exception will be thrown during
* initialization)</td>
* </tr>
* </table>
* </p>
*
* @author John A. Lewis
* @since 2.0
*/
public class ParameterizableViewController extends AbstractController {
private String viewName;
/**
* Set the name of the view to delegate to.
*/
public void setViewName(String viewName) {
this.viewName = viewName;
}
/**
* Return the name of the view to delegate to.
*/
public String getViewName() {
return this.viewName;
}
protected void initApplicationContext() {
if (this.viewName == null) {
throw new IllegalArgumentException("Property 'viewName' is required");
}
}
/**
* Return a ModelAndView object with the specified view name.
*/
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception {
return new ModelAndView(getViewName());
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.ModelAndView;
/**
* <p>Trivial controller that transforms the PortletMode to a view name.
* The advantage here is that the client is not exposed to
* the concrete view technology but rather just to the controller URL;
* the concrete view will be determined by the ViewResolver.</p>
*
* <p>Example: PortletMode.VIEW -> "view"</p>
*
* <p>This controller does not handle action requests.</p>
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @since 2.0
*/
public class PortletModeNameViewController implements Controller {
public void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception {
throw new PortletException("PortletModeNameViewController does not handle action requests");
}
public ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response) {
return new ModelAndView(request.getPortletMode().toString());
}
}

View File

@ -0,0 +1,208 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.context.PortletConfigAware;
import org.springframework.web.portlet.context.PortletContextAware;
/**
* {@link Controller} implementation that wraps a portlet instance which it manages
* internally. Such a wrapped portlet is not known outside of this controller;
* its entire lifecycle is covered here.
*
* <p>Useful to invoke an existing portlet via Spring's dispatching infrastructure,
* for example to apply Spring
* {@link org.springframework.web.portlet.HandlerInterceptor HandlerInterceptors}
* to its requests.
*
* <p><b>Example:</b>
*
* <pre>&lt;bean id="wrappingController" class="org.springframework.web.portlet.mvc.PortletWrappingController"&gt;
* &lt;property name="portletClass"&gt;
* &lt;value&gt;org.springframework.web.portlet.sample.HelloWorldPortlet&lt;/value&gt;
* &lt;/property&gt;
* &lt;property name="portletName"&gt;
* &lt;value&gt;hello-world&lt;/value&gt;
* &lt;/property&gt;
* &lt;property name="initParameters"&gt;
* &lt;props&gt;
* &lt;prop key="config"&gt;/WEB-INF/hello-world-portlet-config.xml&lt;/prop&gt;
* &lt;/props&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
*/
public class PortletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean, PortletContextAware, PortletConfigAware {
private boolean useSharedPortletConfig = true;
private PortletContext portletContext;
private PortletConfig portletConfig;
private Class portletClass;
private String portletName;
private Properties initParameters = new Properties();
private String beanName;
private Portlet portletInstance;
/**
* Set whether to use the shared PortletConfig object passed in
* through <code>setPortletConfig</code>, if available.
* <p>Default is "true". Turn this setting to "false" to pass in
* a mock PortletConfig object with the bean name as portlet name,
* holding the current PortletContext.
* @see #setPortletConfig
*/
public void setUseSharedPortletConfig(boolean useSharedPortletConfig) {
this.useSharedPortletConfig = useSharedPortletConfig;
}
public void setPortletContext(PortletContext portletContext) {
this.portletContext = portletContext;
}
public void setPortletConfig(PortletConfig portletConfig) {
this.portletConfig = portletConfig;
}
/**
* Set the class of the Portlet to wrap.
* Needs to implement <code>javax.portlet.Portlet</code>.
* @see javax.portlet.Portlet
*/
public void setPortletClass(Class portletClass) {
this.portletClass = portletClass;
}
/**
* Set the name of the Portlet to wrap.
* Default is the bean name of this controller.
*/
public void setPortletName(String portletName) {
this.portletName = portletName;
}
/**
* Specify init parameters for the portlet to wrap,
* as name-value pairs.
*/
public void setInitParameters(Properties initParameters) {
this.initParameters = initParameters;
}
public void setBeanName(String name) {
this.beanName = name;
}
public void afterPropertiesSet() throws Exception {
if (this.portletClass == null) {
throw new IllegalArgumentException("portletClass is required");
}
if (!Portlet.class.isAssignableFrom(this.portletClass)) {
throw new IllegalArgumentException("portletClass [" + this.portletClass.getName() +
"] needs to implement interface [javax.portlet.Portlet]");
}
if (this.portletName == null) {
this.portletName = this.beanName;
}
PortletConfig config = this.portletConfig;
if (config == null || !this.useSharedPortletConfig) {
config = new DelegatingPortletConfig();
}
this.portletInstance = (Portlet) this.portletClass.newInstance();
this.portletInstance.init(config);
}
protected void handleActionRequestInternal(
ActionRequest request, ActionResponse response) throws Exception {
this.portletInstance.processAction(request, response);
}
protected ModelAndView handleRenderRequestInternal(
RenderRequest request, RenderResponse response) throws Exception {
this.portletInstance.render(request, response);
return null;
}
public void destroy() {
this.portletInstance.destroy();
}
/**
* Internal implementation of the PortletConfig interface, to be passed
* to the wrapped portlet.
* <p>Delegates to {@link PortletWrappingController} fields
* and methods to provide init parameters and other environment info.
*/
private class DelegatingPortletConfig implements PortletConfig {
public String getPortletName() {
return portletName;
}
public PortletContext getPortletContext() {
return portletContext;
}
public String getInitParameter(String paramName) {
return initParameters.getProperty(paramName);
}
public Enumeration getInitParameterNames() {
return initParameters.keys();
}
public ResourceBundle getResourceBundle(Locale locale) {
return (portletConfig != null ? portletConfig.getResourceBundle(locale) : null);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.HandlerAdapter;
import org.springframework.web.portlet.ModelAndView;
/**
* Adapter to use the Controller workflow interface with the generic DispatcherPortlet.
*
* <p>This is an SPI class, not used directly by application code.
*
* @author John A. Lewis
* @since 2.0
* @see org.springframework.web.portlet.DispatcherPortlet
* @see Controller
*/
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
public void handleAction(ActionRequest request, ActionResponse response, Object handler)
throws Exception {
((Controller) handler).handleActionRequest(request, response);
}
public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRenderRequest(request, response);
}
}

View File

@ -0,0 +1,555 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.portlet.ModelAndView;
/**
* <p>Concrete FormController implementation that provides configurable
* form and success views, and an onSubmit chain for convenient overriding.
* Automatically resubmits to the form view in case of validation errors,
* and renders the success view in case of a valid submission.</p>
*
* <p>The workflow of this Controller does not differ much from the one described
* in the {@link AbstractFormController AbstractFormController}. The difference
* is that you do not need to implement {@link #showForm showForm},
* {@link #processFormSubmission processFormSubmission}, and
* {@link #renderFormSubmission renderFormSubmission}: A form view and a
* success view can be configured declaratively.</p>
*
* <p>This controller is different from it's servlet counterpart in that it must take
* into account the two phases of a portlet request: the action phase and the render
* phase. See the JSR-168 spec for more details on these two phases.
* Be especially aware that the action phase is called only once, but that the
* render phase will be called repeatedly by the portal -- it does this every time
* the page containing the portlet is updated, even if the activity is in some other
* portlet. The main difference in the methods in this class is that the
* <code>onSubmit</code> methods have all been split into <code>onSubmitAction</code>
* and <code>onSubmitRender</code> to account for the two phases.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractFormController.html#workflow">in addition to the superclass</a>):</b><br>
* <ol>
* <li>Call to {@link #processFormSubmission processFormSubmission} which inspects
* the {@link org.springframework.validation.Errors Errors} object to see if
* any errors have occurred during binding and validation.</li>
* <li>If errors occured, the controller will return the configured formView,
* showing the form again (possibly rendering according error messages).</li>
* <li>If {@link #isFormChangeRequest isFormChangeRequest} is overridden and returns
* true for the given request, the controller will return the formView too.
* In that case, the controller will also suppress validation. Before returning the formView,
* the controller will invoke {@link #onFormChange}, giving sub-classes a chance
* to make modification to the command object.
* This is intended for requests that change the structure of the form,
* which should not cause validation and show the form in any case.</li>
* <li>If no errors occurred, the controller will call
* {@link #onSubmitAction(ActionRequest, ActionResponse, Object, BindException) onSubmitAction}
* during the action phase and then {@link #onSubmitRender(RenderRequest, RenderResponse,
* Object, BindException) onSubmitRender} during the render phase, which in case of the
* default implementation delegate to {@link #onSubmitAction(Object, BindException)
* onSubmitAction} and {@link #onSubmitRender(Object, BindException) onSubmitRender}
* with just the command object.
* The default implementation of the latter method will return the configured
* <code>successView</code>. Consider just implementing {@link #doSubmitAction doSubmitAction}
* for simply performing a submit action during the action phase and then rendering
* the success view during the render phase.</li>
* </ol>
* </p>
*
* <p>The submit behavior can be customized by overriding one of the
* {@link #onSubmitAction onSubmitAction} or {@link #onSubmitRender onSubmitRender}
* methods. Submit actions can also perform custom validation if necessary
* (typically database-driven checks), calling {@link #showForm(RenderRequest,
* RenderResponse, BindException) showForm} in case of validation errors to show
* the form view again. You do not have to override both the <code>onSubmitAction</code> and
* <code>onSubmitRender</code> methods at a given level unless you truly have custom logic to
* perform in both.<p>
*
* <p><b>WARNING:</b> Make sure that any one-time system updates (such as database
* updates or file writes) are performed in either an {@link #onSubmitAction onSubmitAction}
* method or the {@link #doSubmitAction doSubmitAction} method. Logic in the
* {@link #onSubmitRender onSubmitRender} methods may be executed repeatedly by
* the portal whenever the page containing the portlet is updated.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractFormController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>formView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when the user asks for a new form
* or when validation errors have occurred on form submission.</td>
* </tr>
* <tr>
* <td>successView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when successful form submissions have
* occurred. Such a success view could e.g. display a submission summary.
* More sophisticated actions can be implemented by overriding one of
* the {@link #onSubmitRender(Object) onSubmitRender()} methods.</td>
* </tr>
* <table>
* </p>
*
* <p>Parameters indicated with <code>setPassRenderParameters</code> will be
* preserved if the form has errors or if a form change request occurs.
* If there are render parameters you need in <code>onSubmitRender</code>,
* then you need to pass those forward from <code>onSubmitAction</code>.
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @author Rob Harrop
* @since 2.0
*/
public class SimpleFormController extends AbstractFormController {
private String formView;
private String successView;
/**
* Create a new SimpleFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, sessionForm, formView,
* successView. Note that commandClass doesn't need to be set when overriding
* <code>formBackingObject</code>, as this determines the class anyway.
* @see #setCommandClass(Class)
* @see #setCommandName(String)
* @see #setSessionForm(boolean)
* @see #setFormView
* @see #setSuccessView
* @see #formBackingObject(PortletRequest)
*/
public SimpleFormController() {
// AbstractFormController sets default cache seconds to 0.
super();
}
/**
* Set the name of the view that should be used for form display.
*/
public final void setFormView(String formView) {
this.formView = formView;
}
/**
* Return the name of the view that should be used for form display.
*/
public final String getFormView() {
return this.formView;
}
/**
* Set the name of the view that should be shown on successful submit.
*/
public final void setSuccessView(String successView) {
this.successView = successView;
}
/**
* Return the name of the view that should be shown on successful submit.
*/
public final String getSuccessView() {
return this.successView;
}
/**
* This implementation shows the configured form view, delegating to the
* analogous showForm version with a controlModel argument.
* <p>Can be called within onSubmit implementations, to redirect back to the form
* in case of custom validation errors (i.e. not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* <p>If calling showForm with a custom control model in subclasses, it's preferable
* to override the analogous showForm version with a controlModel argument
* (which will handle both standard form showing and custom form showing then).
* @see #setFormView
* @see #showForm(RenderRequest, RenderResponse, BindException, Map)
*/
protected ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors)
throws Exception {
return showForm(request, response, errors, null);
}
/**
* This implementation shows the configured form view.
* <p>Can be called within onSubmit implementations, to redirect back to the form
* in case of custom validation errors (i.e. not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* @param request current render request
* @param errors validation errors holder
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #setFormView
*/
protected ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors, Map controlModel)
throws Exception {
return showForm(request, errors, getFormView(), controlModel);
}
/**
* Create a reference data map for the given request and command,
* consisting of bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation delegates to {@link #referenceData(PortletRequest)}.
* Subclasses can override this to set reference data used in the view.
* @param request current portlet request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
protected Map referenceData(PortletRequest request, Object command, Errors errors) throws Exception {
return referenceData(request);
}
/**
* Create a reference data map for the given request.
* Called by referenceData version with all parameters.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current portlet request
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see #referenceData(PortletRequest, Object, Errors)
* @see ModelAndView
*/
protected Map referenceData(PortletRequest request) throws Exception {
return null;
}
/**
* This implementation calls <code>showForm</code> in case of errors,
* and delegates to <code>onSubmitRender<code>'s full version else.
* <p>This can only be overridden to check for an action that should be executed
* without respect to binding errors, like a cancel action. To just handle successful
* submissions without binding errors, override one of the <code>onSubmitRender</code>
* methods.
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitRender(Object, BindException)
* @see #onSubmitRender(Object)
* @see #processFormSubmission(ActionRequest, ActionResponse, Object, BindException)
*/
protected ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors() || isFormChangeRequest(request)) {
return showForm(request, response, errors);
}
else {
return onSubmitRender(request, response, command, errors);
}
}
/**
* This implementation does nothing in case of errors,
* and delegates to <code>onSubmitAction</code>'s full version else.
* <p>This can only be overridden to check for an action that should be executed
* without respect to binding errors, like a cancel action. To just handle successful
* submissions without binding errors, override one of the <code>onSubmitAction</code>
* methods or <code>doSubmitAction</code>.
* @see #showForm
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitAction(Object, BindException)
* @see #onSubmitAction(Object)
* @see #doSubmitAction(Object)
* @see #renderFormSubmission(RenderRequest, RenderResponse, Object, BindException)
*/
protected void processFormSubmission(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors()) {
if (logger.isDebugEnabled()) {
logger.debug("Data binding errors: " + errors.getErrorCount());
}
if (isRedirectAction()) {
setFormSubmit(response);
}
passRenderParameters(request, response);
}
else if (isFormChangeRequest(request)) {
logger.debug("Detected form change request -> routing request to onFormChange");
if (isRedirectAction()) {
setFormSubmit(response);
}
passRenderParameters(request, response);
onFormChange(request, response, command, errors);
}
else {
logger.debug("No errors - processing submit");
onSubmitAction(request, response, command, errors);
}
}
/**
* This implementation delegates to {@link #isFormChangeRequest}:
* A form change request changes the appearance of the form
* and should not get validated but just show the new form.
* @see #isFormChangeRequest
*/
protected boolean suppressValidation(PortletRequest request) {
return isFormChangeRequest(request);
}
/**
* Determine whether the given request is a form change request.
* A form change request changes the appearance of the form
* and should always show the new form, without validation.
* <p>Gets called by {@link #suppressValidation} and {@link #processFormSubmission}.
* Consequently, this single method determines to suppress validation
* <i>and</i> to show the form view in any case.
* <p>The default implementation returns <code>false</code.
* @param request current portlet request
* @return whether the given request is a form change request
* @see #suppressValidation
* @see #processFormSubmission
*/
protected boolean isFormChangeRequest(PortletRequest request) {
return false;
}
/**
* Called during form submission if {@link #isFormChangeRequest(PortletRequest)}
* returns <code>true</code>. Allows subclasses to implement custom logic
* to modify the command object to directly modify data in the form.
* <p>The default implementation delegates to
* <code>onFormChange(request, response, command)</code>.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of errors
* @see #isFormChangeRequest(PortletRequest)
* @see #onFormChange(ActionRequest, ActionResponse, Object)
*/
protected void onFormChange(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
onFormChange(request, response, command);
}
/**
* Simpler <code>onFormChange</code> variant, called by the full version
* <code>onFormChange(request, response, command, errors)</code>.
* <p>The default implementation is empty.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onFormChange(ActionRequest, ActionResponse, Object, BindException)
*/
protected void onFormChange(ActionRequest request, ActionResponse response, Object command)
throws Exception {
}
/**
* Submit render phase callback with all parameters. Called in case of submit without errors
* reported by the registered validator, or on every submit if no validator.
* <p>The default implementation delegates to {@link #onSubmitRender(Object, BindException)}.
* For simply performing a submit action and rendering the specified success view,
* do not implement an <code>onSubmitRender</code> at all.
* <p>Subclasses can override this to provide custom rendering to display results of
* the action phase. Implementations can also call <code>showForm</code> to return to the form
* if the <code>onSubmitAction</code> failed custom validation. Do <i>not</i> implement multiple
* <code>onSubmitRender</code> methods: In that case,
* just this method will be called by the controller.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current render request
* @param response current render response
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors (subclass can add errors if it wants to)
* @return the prepared model and view
* @throws Exception in case of errors
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitRender(Object, BindException)
* @see #doSubmitAction
* @see #showForm
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmitRender(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
return onSubmitRender(command, errors);
}
/**
* Submit action phase callback with all parameters. Called in case of submit without errors
* reported by the registered validator respectively on every submit if no validator.
* <p>The default implementation delegates to {@link #onSubmitAction(Object, BindException)}.
* For simply performing a submit action consider implementing <code>doSubmitAction</code>
* rather than an <code>onSubmitAction</code> version.
* <p>Subclasses can override this to provide custom submission handling like storing
* the object to the database. Implementations can also perform custom validation and
* signal the render phase to call <code>showForm</code> to return to the form. Do <i>not</i>
* implement multiple <code>onSubmitAction</code> methods: In that case,
* just this method will be called by the controller.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors (subclass can add errors if it wants to)
* @throws Exception in case of errors
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitAction(Object, BindException)
* @see #doSubmitAction
* @see org.springframework.validation.Errors
*/
protected void onSubmitAction(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
onSubmitAction(command, errors);
}
/**
* Simpler <code>onSubmitRender</code> version. Called by the default implementation
* of the <code>onSubmitRender</code> version with all parameters.
* <p>The default implementation calls {@link #onSubmitRender(Object)}, using the
* returned ModelAndView if actually implemented in a subclass. Else, the
* default behavior will apply: rendering the success view with the command
* and Errors instance as model.
* <p>Subclasses can override this to provide custom submission handling that
* does not need request and response.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors
* @return the prepared model and view, or null
* @throws Exception in case of errors
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitRender(Object)
* @see #onSubmitAction(Object, BindException)
* @see #setSuccessView
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmitRender(Object command, BindException errors) throws Exception {
ModelAndView mv = onSubmitRender(command);
if (mv != null) {
// simplest onSubmit version implemented in custom subclass
return mv;
}
else {
// default behavior: render success view
if (getSuccessView() == null) {
throw new PortletException("successView isn't set");
}
return new ModelAndView(getSuccessView(), errors.getModel());
}
}
/**
* Simpler <code>onSubmitAction</code> version. Called by the default implementation
* of the <code>onSubmitAction</code> version with all parameters.
* <p>The default implementation calls {@link #onSubmitAction(Object)}.
* <p>Subclasses can override this to provide custom submission handling that
* does not need request and response.
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors
* @throws Exception in case of errors
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitAction(Object)
* @see #onSubmitRender(Object, BindException)
* @see org.springframework.validation.Errors
*/
protected void onSubmitAction(Object command, BindException errors) throws Exception {
onSubmitAction(command);
}
/**
* Simplest <code>onSubmitRender</code> version. Called by the default implementation
* of the <code>onSubmitRender</code> version with command and BindException parameters.
* <p>This implementation returns null as ModelAndView, making the calling
* <code>onSubmitRender</code> method perform its default rendering of the success view.
* <p>Subclasses can override this to provide custom submission handling
* that just depends on the command object.
* @param command form object with request parameters bound onto it
* @return the prepared model and view, or null for default (i.e. successView)
* @throws Exception in case of errors
* @see #onSubmitRender(Object, BindException)
* @see #onSubmitAction(Object)
* @see #doSubmitAction
* @see #setSuccessView
*/
protected ModelAndView onSubmitRender(Object command) throws Exception {
return null;
}
/**
* Simplest <code>onSubmitAction</code> version. Called by the default implementation
* of the <code>onSubmitAction</code> version with command and BindException parameters.
* <p>This implementation calls <code>doSubmitAction</code>.
* <p>Subclasses can override this to provide custom submission handling
* that just depends on the command object.
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onSubmitAction(Object, BindException)
* @see #onSubmitRender(Object)
* @see #doSubmitAction
*/
protected void onSubmitAction(Object command) throws Exception {
doSubmitAction(command);
}
/**
* Template method for submit actions. Called by the default implementation
* of the simplest <code>onSubmitAction</code> version.
* <p><b>This is the preferred submit callback to implement if you want to
* perform an action (like storing changes to the database) and then render
* the success view with the command and Errors instance as model.</b>
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onSubmitAction(Object)
* @see #onSubmitRender(Object)
* @see #setSuccessView
*/
protected void doSubmitAction(Object command) throws Exception {
}
}

View File

@ -0,0 +1,606 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc.annotation;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.portlet.WindowState;
import org.springframework.beans.BeanUtils;
import org.springframework.core.Conventions;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.portlet.HandlerAdapter;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.MissingPortletRequestParameterException;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.context.PortletWebRequest;
import org.springframework.web.portlet.handler.PortletContentGenerator;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
import org.springframework.web.portlet.util.PortletUtils;
import org.springframework.web.servlet.View;
/**
* Implementation of the {@link org.springframework.web.portlet.HandlerAdapter}
* interface that maps handler methods based on portlet modes, action/render phases
* and request parameters expressed through the {@link RequestMapping} annotation.
*
* <p>Supports request parameter binding through the {@link RequestParam} annotation.
* Also supports the {@link ModelAttribute} annotation for exposing model attribute
* values to the view, as well as {@link InitBinder} for binder initialization methods
* and {@link SessionAttributes} for automatic session management of specific attributes.
*
* <p>This adapter can be customized through various bean properties.
* A common use case is to apply shared binder initialization logic through
* a custom {@link #setWebBindingInitializer WebBindingInitializer}.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5
* @see #setWebBindingInitializer
* @see #setSessionAttributeStore
*/
public class AnnotationMethodHandlerAdapter extends PortletContentGenerator implements HandlerAdapter {
private static final String IMPLICIT_MODEL_ATTRIBUTE = "org.springframework.web.portlet.mvc.ImplicitModel";
private WebBindingInitializer webBindingInitializer;
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
private int cacheSecondsForSessionAttributeHandlers = 0;
private boolean synchronizeOnSession = false;
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private WebArgumentResolver[] customArgumentResolvers;
private final Map<Class<?>, PortletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, PortletHandlerMethodResolver>();
/**
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Specify the strategy to store session attributes with.
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the PortletSession, using the same
* attribute name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
this.sessionAttributeStore = sessionAttributeStore;
}
/**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all general
* handlers (but not to <code>@SessionAttributes</code> annotated handlers), this
* setting will apply to <code>@SessionAttributes</code> annotated handlers only.
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
*/
public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
}
/**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of each handler method will get
* synchronized if this flag is "true". The best available session mutex
* will be used for the synchronization; ideally, this will be a mutex
* exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the PortletSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession)
*/
public void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter
* names if needed (e.g. for default attribute names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Set a custom ArgumentResolvers to use for special method parameter types.
* Such a custom ArgumentResolver will kick in first, having a chance to
* resolve an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
}
/**
* Set one or more custom ArgumentResolvers to use for special method
* parameter types. Any such custom ArgumentResolver will kick in first,
* having a chance to resolve an argument value before the standard
* argument handling kicks in.
*/
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}
public void handleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception {
Object returnValue = doHandle(request, response, handler);
if (returnValue != null) {
throw new IllegalStateException("Invalid action method return value: " + returnValue);
}
}
public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception {
checkAndPrepare(request, response);
return doHandle(request, response, handler);
}
protected ModelAndView doHandle(PortletRequest request, PortletResponse response, Object handler) throws Exception {
ExtendedModelMap implicitModel = null;
if (request instanceof RenderRequest && response instanceof RenderResponse) {
RenderRequest renderRequest = (RenderRequest) request;
RenderResponse renderResponse = (RenderResponse) response;
// Detect implicit model from associated action phase.
if (renderRequest.getParameter(IMPLICIT_MODEL_ATTRIBUTE) != null) {
PortletSession session = request.getPortletSession(false);
if (session != null) {
implicitModel = (ExtendedModelMap) session.getAttribute(IMPLICIT_MODEL_ATTRIBUTE);
}
}
if (handler.getClass().getAnnotation(SessionAttributes.class) != null) {
// Always prevent caching in case of session attribute management.
checkAndPrepare(renderRequest, renderResponse, this.cacheSecondsForSessionAttributeHandlers);
}
else {
// Uses configured default cacheSeconds setting.
checkAndPrepare(renderRequest, renderResponse);
}
}
if (implicitModel == null) {
implicitModel = new BindingAwareModelMap();
}
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
PortletSession session = request.getPortletSession(false);
if (session != null) {
Object mutex = PortletUtils.getSessionMutex(session);
synchronized (mutex) {
return invokeHandlerMethod(request, response, handler, implicitModel);
}
}
}
return invokeHandlerMethod(request, response, handler, implicitModel);
}
private ModelAndView invokeHandlerMethod(
PortletRequest request, PortletResponse response, Object handler, ExtendedModelMap implicitModel)
throws Exception {
PortletWebRequest webRequest = new PortletWebRequest(request, response);
PortletHandlerMethodResolver methodResolver = getMethodResolver(handler);
Method handlerMethod = methodResolver.resolveHandlerMethod(request, response);
PortletHandlerMethodInvoker methodInvoker = new PortletHandlerMethodInvoker(methodResolver);
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel);
methodInvoker.updateModelAttributes(
handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
// Expose implicit model for subsequent render phase.
if (response instanceof ActionResponse && !implicitModel.isEmpty()) {
ActionResponse actionResponse = (ActionResponse) response;
try {
actionResponse.setRenderParameter(IMPLICIT_MODEL_ATTRIBUTE, Boolean.TRUE.toString());
request.getPortletSession().setAttribute(IMPLICIT_MODEL_ATTRIBUTE, implicitModel);
}
catch (IllegalStateException ex) {
// Probably sendRedirect called... no need to expose model to render phase.
}
}
return mav;
}
/**
* Template method for creating a new PortletRequestDataBinder instance.
* <p>The default implementation creates a standard PortletRequestDataBinder.
* This can be overridden for custom PortletRequestDataBinder subclasses.
* @param request current portlet request
* @param target the target object to bind onto (or <code>null</code>
* if the binder is just used to convert a plain parameter value)
* @param objectName the objectName of the target object
* @return the PortletRequestDataBinder instance to use
* @throws Exception in case of invalid state or arguments
* @see PortletRequestDataBinder#bind(javax.portlet.PortletRequest)
* @see PortletRequestDataBinder#convertIfNecessary(Object, Class, MethodParameter)
*/
protected PortletRequestDataBinder createBinder(
PortletRequest request, Object target, String objectName) throws Exception {
return new PortletRequestDataBinder(target, objectName);
}
/**
* Build a HandlerMethodResolver for the given handler type.
*/
private PortletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler);
PortletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
if (resolver == null) {
resolver = new PortletHandlerMethodResolver(handlerClass);
this.methodResolverCache.put(handlerClass, resolver);
}
return resolver;
}
private static class PortletHandlerMethodResolver extends HandlerMethodResolver {
public PortletHandlerMethodResolver(Class<?> handlerType) {
super(handlerType);
}
public Method resolveHandlerMethod(PortletRequest request, PortletResponse response) throws PortletException {
String lookupMode = request.getPortletMode().toString();
Map<RequestMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestMappingInfo, Method>();
for (Method handlerMethod : getHandlerMethods()) {
RequestMapping mapping = AnnotationUtils.findAnnotation(handlerMethod, RequestMapping.class);
RequestMappingInfo mappingInfo = new RequestMappingInfo();
mappingInfo.modes = mapping.value();
mappingInfo.params = mapping.params();
mappingInfo.action = isActionMethod(handlerMethod);
mappingInfo.render = isRenderMethod(handlerMethod);
boolean match = false;
if (mappingInfo.modes.length > 0) {
for (String mappedMode : mappingInfo.modes) {
if (mappedMode.equalsIgnoreCase(lookupMode)) {
if (checkParameters(request, response, mappingInfo)) {
match = true;
}
else {
break;
}
}
}
}
else {
// No modes specified: parameter match sufficient.
match = checkParameters(request, response, mappingInfo);
}
if (match) {
Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
throw new IllegalStateException("Ambiguous handler methods mapped for portlet mode '" +
lookupMode + "': {" + oldMappedMethod + ", " + handlerMethod +
"}. If you intend to handle the same mode in multiple methods, then factor " +
"them out into a dedicated handler class with that mode mapped at the type level!");
}
}
}
if (!targetHandlerMethods.isEmpty()) {
if (targetHandlerMethods.size() == 1) {
return targetHandlerMethods.values().iterator().next();
}
else {
RequestMappingInfo bestMappingMatch = null;
for (RequestMappingInfo mapping : targetHandlerMethods.keySet()) {
if (bestMappingMatch == null) {
bestMappingMatch = mapping;
}
else {
if ((bestMappingMatch.modes.length == 0 && mapping.modes.length > 0) ||
bestMappingMatch.params.length < mapping.params.length) {
bestMappingMatch = mapping;
}
}
}
return targetHandlerMethods.get(bestMappingMatch);
}
}
else {
throw new UnavailableException("No matching handler method found for portlet request: mode '" +
request.getPortletMode() + "', type '" + (response instanceof ActionResponse ? "action" : "render") +
"', parameters " + StylerUtils.style(request.getParameterMap()));
}
}
private boolean checkParameters(PortletRequest request, PortletResponse response, RequestMappingInfo mapping) {
if (response instanceof RenderResponse) {
if (mapping.action) {
return false;
}
}
else if (response instanceof ActionResponse) {
if (mapping.render) {
return false;
}
}
return PortletAnnotationMappingUtils.checkParameters(mapping.params, request);
}
private boolean isActionMethod(Method handlerMethod) {
if (!void.class.equals(handlerMethod.getReturnType())) {
return false;
}
for (Class<?> argType : handlerMethod.getParameterTypes()) {
if (ActionRequest.class.isAssignableFrom(argType) || ActionResponse.class.isAssignableFrom(argType) ||
InputStream.class.isAssignableFrom(argType) || Reader.class.isAssignableFrom(argType)) {
return true;
}
}
return false;
}
private boolean isRenderMethod(Method handlerMethod) {
if (!void.class.equals(handlerMethod.getReturnType())) {
return true;
}
for (Class<?> argType : handlerMethod.getParameterTypes()) {
if (RenderRequest.class.isAssignableFrom(argType) || RenderResponse.class.isAssignableFrom(argType) ||
OutputStream.class.isAssignableFrom(argType) || Writer.class.isAssignableFrom(argType)) {
return true;
}
}
return false;
}
}
private class PortletHandlerMethodInvoker extends HandlerMethodInvoker {
public PortletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore,
parameterNameDiscoverer, customArgumentResolvers);
}
@Override
protected void raiseMissingParameterException(String paramName, Class paramType) throws Exception {
throw new MissingPortletRequestParameterException(paramName, paramType.getName());
}
@Override
protected void raiseSessionRequiredException(String message) throws Exception {
throw new PortletSessionRequiredException(message);
}
@Override
protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
throws Exception {
return AnnotationMethodHandlerAdapter.this.createBinder(
(PortletRequest) webRequest.getNativeRequest(), target, objectName);
}
@Override
protected void doBind(NativeWebRequest webRequest, WebDataBinder binder, boolean failOnErrors)
throws Exception {
PortletRequestDataBinder servletBinder = (PortletRequestDataBinder) binder;
servletBinder.bind((PortletRequest) webRequest.getNativeRequest());
if (failOnErrors) {
servletBinder.closeNoCatch();
}
}
@Override
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)
throws Exception {
PortletRequest request = (PortletRequest) webRequest.getNativeRequest();
PortletResponse response = (PortletResponse) webRequest.getNativeResponse();
if (PortletRequest.class.isAssignableFrom(parameterType)) {
return request;
}
else if (PortletResponse.class.isAssignableFrom(parameterType)) {
return response;
}
else if (PortletSession.class.isAssignableFrom(parameterType)) {
return request.getPortletSession();
}
else if (PortletPreferences.class.isAssignableFrom(parameterType)) {
return request.getPreferences();
}
else if (PortletMode.class.isAssignableFrom(parameterType)) {
return request.getPortletMode();
}
else if (WindowState.class.isAssignableFrom(parameterType)) {
return request.getWindowState();
}
else if (PortalContext.class.isAssignableFrom(parameterType)) {
return request.getPortalContext();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(parameterType)) {
return request.getLocale();
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
if (!(request instanceof ActionRequest)) {
throw new IllegalStateException("InputStream can only get obtained for ActionRequest");
}
return ((ActionRequest) request).getPortletInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
if (!(request instanceof ActionRequest)) {
throw new IllegalStateException("Reader can only get obtained for ActionRequest");
}
return ((ActionRequest) request).getReader();
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
if (!(response instanceof RenderResponse)) {
throw new IllegalStateException("OutputStream can only get obtained for RenderResponse");
}
return ((RenderResponse) response).getPortletOutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
if (!(response instanceof RenderResponse)) {
throw new IllegalStateException("Writer can only get obtained for RenderResponse");
}
return ((RenderResponse) response).getWriter();
}
return super.resolveStandardArgument(parameterType, webRequest);
}
@SuppressWarnings("unchecked")
public ModelAndView getModelAndView(
Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel) {
if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
else if (returnValue instanceof org.springframework.web.servlet.ModelAndView) {
org.springframework.web.servlet.ModelAndView smav = (org.springframework.web.servlet.ModelAndView) returnValue;
ModelAndView mav = (smav.isReference() ?
new ModelAndView(smav.getViewName(), smav.getModelMap()) :
new ModelAndView(smav.getView(), smav.getModelMap()));
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
else if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
}
else if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);
}
else if (returnValue instanceof View) {
return new ModelAndView(returnValue).addAllObjects(implicitModel);
}
else if (returnValue instanceof String) {
return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
}
else if (returnValue == null) {
// Either returned null or was 'void' return.
return null;
}
else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
// Assume a single model attribute...
ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class);
String attrName = (attr != null ? attr.value() : "");
ModelAndView mav = new ModelAndView().addAllObjects(implicitModel);
if ("".equals(attrName)) {
Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType);
attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue);
}
return mav.addObject(attrName, returnValue);
}
else {
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
}
}
}
private static class RequestMappingInfo {
public String[] modes = new String[0];
public String[] params = new String[0];
private boolean action = false;
private boolean render = false;
public boolean equals(Object obj) {
RequestMappingInfo other = (RequestMappingInfo) obj;
return (this.action == other.action && this.render == other.render &&
Arrays.equals(this.modes, other.modes) && Arrays.equals(this.params, other.params));
}
public int hashCode() {
return (Arrays.hashCode(this.modes) * 29 + Arrays.hashCode(this.params));
}
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc.annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.generic.GenericBeanFactoryAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.handler.AbstractMapBasedHandlerMapping;
/**
* Implementation of the {@link org.springframework.web.portlet.HandlerMapping}
* interface that maps handlers based on portlet modes expressed through the
* {@link RequestMapping} annotation at the type or method level.
*
* <p>Registered by default in {@link org.springframework.web.portlet.DispatcherPortlet}
* on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
* DispatcherPortlet context, you need to add a DefaultAnnotationHandlerMapping bean
* explicitly, since custom HandlerMapping beans replace the default mapping strategies.
* Defining a DefaultAnnotationHandlerMapping also allows for registering custom
* interceptors:
*
* <pre class="code">
* &lt;bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
* &lt;property name="interceptors"&gt;
* ...
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* Annotated controllers are usually marked with the {@link Controller} stereotype
* at the type level. This is not strictly necessary when {@link RequestMapping} is
* applied at the type level (since such a handler usually implements the
* {@link org.springframework.web.portlet.mvc.Controller} interface). However,
* {@link Controller} is required for detecting {@link RequestMapping} annotations
* at the method level.
*
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
* expressed at the class level (if any). Portlet modes need to uniquely map onto
* specific handler beans, with any given portlet mode only allowed to be mapped
* onto one specific handler bean (not spread across multiple handler beans).
* It is strongly recommended to co-locate related handler methods into the same bean.
*
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
* annotated handler methods, as mapped by this HandlerMapping. For
* {@link RequestMapping} at the type level, specific HandlerAdapters such as
* {@link org.springframework.web.portlet.mvc.SimpleControllerHandlerAdapter} apply.
*
* @author Juergen Hoeller
* @since 2.5
* @see RequestMapping
* @see AnnotationMethodHandlerAdapter
*/
public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapping {
/**
* Calls the <code>registerHandlers</code> method in addition
* to the superclass's initialization.
* @see #detectHandlers
*/
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
detectHandlers();
}
/**
* Register all handlers specified in the Portlet mode map for the corresponding modes.
* @throws org.springframework.beans.BeansException if the handler couldn't be registered
*/
protected void detectHandlers() throws BeansException {
ApplicationContext context = getApplicationContext();
String[] beanNames = context.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Class<?> handlerType = context.getType(beanName);
ListableBeanFactory bf = (context instanceof ConfigurableApplicationContext ?
((ConfigurableApplicationContext) context).getBeanFactory() : context);
GenericBeanFactoryAccessor bfa = new GenericBeanFactoryAccessor(bf);
RequestMapping mapping = bfa.findAnnotationOnBean(beanName, RequestMapping.class);
if (mapping != null) {
String[] modeKeys = mapping.value();
String[] params = mapping.params();
boolean registerHandlerType = true;
if (modeKeys.length == 0 || params.length == 0) {
registerHandlerType = !detectHandlerMethods(handlerType, beanName, mapping);
}
if (registerHandlerType) {
ParameterMappingPredicate predicate = new ParameterMappingPredicate(params);
for (String modeKey : modeKeys) {
registerHandler(new PortletMode(modeKey), beanName, predicate);
}
}
}
else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
detectHandlerMethods(handlerType, beanName, mapping);
}
}
}
/**
* Derive portlet mode mappings from the handler's method-level mappings.
* @param handlerType the handler type to introspect
* @param beanName the name of the bean introspected
* @param typeMapping the type level mapping (if any)
* @return <code>true</code> if at least 1 handler method has been registered;
* <code>false</code> otherwise
*/
protected boolean detectHandlerMethods(Class handlerType, final String beanName, final RequestMapping typeMapping) {
final Set<Boolean> handlersRegistered = new HashSet<Boolean>(1);
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
if (mapping != null) {
String[] modeKeys = mapping.value();
if (modeKeys.length == 0) {
if (typeMapping != null) {
modeKeys = typeMapping.value();
}
else {
throw new IllegalStateException(
"No portlet mode mappings specified - neither at type nor method level");
}
}
String[] params = mapping.params();
if (typeMapping != null) {
PortletAnnotationMappingUtils.validateModeMapping(modeKeys, typeMapping.value());
params = StringUtils.mergeStringArrays(typeMapping.params(), params);
}
ParameterMappingPredicate predicate = new ParameterMappingPredicate(params);
for (String modeKey : modeKeys) {
registerHandler(new PortletMode(modeKey), beanName, predicate);
handlersRegistered.add(Boolean.TRUE);
}
}
}
});
return !handlersRegistered.isEmpty();
}
/**
* Uses the current PortletMode as lookup key.
*/
protected Object getLookupKey(PortletRequest request) throws Exception {
return request.getPortletMode();
}
/**
* Predicate that matches against parameter conditions.
*/
private static class ParameterMappingPredicate implements PortletRequestMappingPredicate {
private final String[] params;
private ParameterMappingPredicate(String[] params) {
this.params = params;
}
public boolean match(PortletRequest request) {
return PortletAnnotationMappingUtils.checkParameters(this.params, request);
}
public int compareTo(Object other) {
if (other instanceof PortletRequestMappingPredicate) {
return new Integer(((ParameterMappingPredicate) other).params.length).compareTo(this.params.length);
}
else {
return 0;
}
}
public String toString() {
return StringUtils.arrayToCommaDelimitedString(this.params);
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.mvc.annotation;
import javax.portlet.PortletRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.web.portlet.util.PortletUtils;
/**
* Helper class for annotation-based request mapping.
*
* @author Juergen Hoeller
* @since 2.5.2
*/
abstract class PortletAnnotationMappingUtils {
/**
* Check whether the given request matches the specified request methods.
* @param modes the mapped portlet modes to check
* @param typeLevelModes the type-level mode mappings to check against
*/
public static boolean validateModeMapping(String[] modes, String[] typeLevelModes) {
if (!ObjectUtils.isEmpty(modes)) {
for (String mode : modes) {
boolean match = false;
for (String typeLevelMode : typeLevelModes) {
if (mode.equalsIgnoreCase(typeLevelMode)) {
match = true;
}
}
if (!match) {
return false;
}
}
}
return true;
}
/**
* Check whether the given request matches the specified parameter conditions.
* @param params the parameter conditions, following
* {@link org.springframework.web.bind.annotation.RequestMapping#params()}
* @param request the current HTTP request to check
*/
public static boolean checkParameters(String[] params, PortletRequest request) {
if (!ObjectUtils.isEmpty(params)) {
for (String param : params) {
int separator = param.indexOf('=');
if (separator == -1) {
if (param.startsWith("!")) {
if (PortletUtils.hasSubmitParameter(request, param.substring(1))) {
return false;
}
}
else if (!PortletUtils.hasSubmitParameter(request, param)) {
return false;
}
}
else {
String key = param.substring(0, separator);
String value = param.substring(separator + 1);
if (!value.equals(request.getParameter(key))) {
return false;
}
}
}
}
return true;
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Support package for annotation-based Portlet MVC controllers.
</body>
</html>

View File

@ -0,0 +1,33 @@
<html>
<body>
<p>
Standard controller implementations for the portlet MVC framework that
comes with Spring. Provides both abstract base classes and concrete
implementations for often seen use cases.
</p>
<p>
A <code>Controller</code> - as defined in this package - is analogous to a Struts
<code>Action</code>. Usually <code>Controllers</code> are JavaBeans
to allow easy configuration using the {@link org.springframework.beans org.springframework.beans}
package. Controllers define the <code>C</code> from so-called MVC paradigm
and can be used in conjunction with the {@link org.springframework.web.portlet.ModelAndView ModelAndView}
to achieve interactive applications. The view might be represented by a
HTML interface, but, because of model and the controller being completely
independent of the view, PDF views are possible, as well as for instance Excel
views.
</p>
<p>
Especially useful to read, while getting into the Spring MVC framework
are the following:
<ul>
<li><a href="Controller.html">Controller</a></li>
<li><a href="SimpleFormController.html">BaseCommandController</a></li>
<li><a href="ParameterizableViewController.html">ParameterizableViewController</a></li>
</ul>
</p>
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<body>
Provides JSR-168 portlets that integrate with the application context
infrastructure, and the core interfaces and classes for the Portlet
variant of Spring's web MVC framework.
</body>
</html>

View File

@ -0,0 +1,78 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.portlet.ActionRequest;
/**
* Simple wrapper for a Portlet {@link javax.portlet.ActionRequest},
* delegating all calls to the underlying request.
*
* <p>(In the style of the Servlet API's {@link javax.servlet.http.HttpServletRequestWrapper}.)
*
* @author Juergen Hoeller
* @since 2.0
* @see ActionRequestWrapper
* @see javax.servlet.http.HttpServletRequestWrapper
*/
public class ActionRequestWrapper extends PortletRequestWrapper implements ActionRequest {
/** Original request that we're delegating to */
private final ActionRequest actionRequest;
/**
* Create a ActionRequestWrapper for the given request.
* @param request the original request to wrap
* @throws IllegalArgumentException if the supplied <code>request</code> is <code>null</code>
*/
public ActionRequestWrapper(ActionRequest request) {
super(request);
this.actionRequest = request;
}
public InputStream getPortletInputStream() throws IOException {
return this.actionRequest.getPortletInputStream();
}
public void setCharacterEncoding(String enc) throws UnsupportedEncodingException {
this.actionRequest.setCharacterEncoding(enc);
}
public BufferedReader getReader() throws IOException {
return this.actionRequest.getReader();
}
public String getCharacterEncoding() {
return this.actionRequest.getCharacterEncoding();
}
public String getContentType() {
return this.actionRequest.getContentType();
}
public int getContentLength() {
return this.actionRequest.getContentLength();
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.util;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.security.Principal;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.WindowState;
import org.springframework.util.Assert;
/**
* Simple wrapper for a {@link javax.portlet.PortletRequest}, delegating all
* calls to the underlying request.
*
* <p>(In the style of the Servlet API's {@link javax.servlet.ServletRequestWrapper}.)
*
* @author Juergen Hoeller
* @since 2.0
* @see ActionRequestWrapper
* @see javax.servlet.ServletRequestWrapper
*/
public class PortletRequestWrapper implements PortletRequest {
/** Original request that we're delegating to */
private final PortletRequest portletRequest;
/**
* Create a PortletRequestWrapper for the given {@link javax.portlet.PortletRequest}.
* @param request the original {@link javax.portlet.PortletRequest} to wrap
* @throws IllegalArgumentException if the supplied <code>request</code> is <code>null</code>
*/
public PortletRequestWrapper(PortletRequest request) {
Assert.notNull(request, "Request is required");
this.portletRequest = request;
}
public boolean isWindowStateAllowed(WindowState state) {
return this.portletRequest.isWindowStateAllowed(state);
}
public boolean isPortletModeAllowed(PortletMode mode) {
return this.portletRequest.isPortletModeAllowed(mode);
}
public PortletMode getPortletMode() {
return this.portletRequest.getPortletMode();
}
public WindowState getWindowState() {
return this.portletRequest.getWindowState();
}
public PortletPreferences getPreferences() {
return this.portletRequest.getPreferences();
}
public PortletSession getPortletSession() {
return this.portletRequest.getPortletSession();
}
public PortletSession getPortletSession(boolean create) {
return this.portletRequest.getPortletSession(create);
}
public String getProperty(String name) {
return this.portletRequest.getProperty(name);
}
public Enumeration getProperties(String name) {
return this.portletRequest.getProperties(name);
}
public Enumeration getPropertyNames() {
return this.portletRequest.getPropertyNames();
}
public PortalContext getPortalContext() {
return this.portletRequest.getPortalContext();
}
public String getAuthType() {
return this.portletRequest.getAuthType();
}
public String getContextPath() {
return this.portletRequest.getContextPath();
}
public String getRemoteUser() {
return this.portletRequest.getRemoteUser();
}
public Principal getUserPrincipal() {
return this.portletRequest.getUserPrincipal();
}
public boolean isUserInRole(String role) {
return this.portletRequest.isUserInRole(role);
}
public Object getAttribute(String name) {
return this.portletRequest.getAttribute(name);
}
public Enumeration getAttributeNames() {
return this.portletRequest.getAttributeNames();
}
public String getParameter(String name) {
return this.portletRequest.getParameter(name);
}
public Enumeration getParameterNames() {
return this.portletRequest.getParameterNames();
}
public String[] getParameterValues(String name) {
return this.portletRequest.getParameterValues(name);
}
public Map getParameterMap() {
return this.portletRequest.getParameterMap();
}
public boolean isSecure() {
return this.portletRequest.isSecure();
}
public void setAttribute(String name, Object value) {
this.portletRequest.setAttribute(name, value);
}
public void removeAttribute(String name) {
this.portletRequest.removeAttribute(name);
}
public String getRequestedSessionId() {
return this.portletRequest.getRequestedSessionId();
}
public boolean isRequestedSessionIdValid() {
return this.portletRequest.isRequestedSessionIdValid();
}
public String getResponseContentType() {
return this.portletRequest.getResponseContentType();
}
public Enumeration getResponseContentTypes() {
return this.portletRequest.getResponseContentTypes();
}
public Locale getLocale() {
return this.portletRequest.getLocale();
}
public Enumeration getLocales() {
return this.portletRequest.getLocales();
}
public String getScheme() {
return this.portletRequest.getScheme();
}
public String getServerName() {
return this.portletRequest.getServerName();
}
public int getServerPort() {
return this.portletRequest.getServerPort();
}
}

View File

@ -0,0 +1,439 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.portlet.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;
/**
* Miscellaneous utilities for portlet applications.
* Used by various framework classes.
*
* @author Juergen Hoeller
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @since 2.0
*/
public abstract class PortletUtils {
/**
* Return the temporary directory for the current web application,
* as provided by the portlet container.
* @param portletContext the portlet context of the web application
* @return the File representing the temporary directory
*/
public static File getTempDir(PortletContext portletContext) {
Assert.notNull(portletContext, "PortletContext must not be null");
return (File) portletContext.getAttribute(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE);
}
/**
* Return the real path of the given path within the web application,
* as provided by the portlet container.
* <p>Prepends a slash if the path does not already start with a slash,
* and throws a {@link java.io.FileNotFoundException} if the path cannot
* be resolved to a resource (in contrast to
* {@link javax.portlet.PortletContext#getRealPath PortletContext's <code>getRealPath</code>},
* which simply returns <code>null</code>).
* @param portletContext the portlet context of the web application
* @param path the relative path within the web application
* @return the corresponding real path
* @throws FileNotFoundException if the path cannot be resolved to a resource
* @see javax.portlet.PortletContext#getRealPath
*/
public static String getRealPath(PortletContext portletContext, String path) throws FileNotFoundException {
Assert.notNull(portletContext, "PortletContext must not be null");
// Interpret location as relative to the web application root directory.
if (!path.startsWith("/")) {
path = "/" + path;
}
String realPath = portletContext.getRealPath(path);
if (realPath == null) {
throw new FileNotFoundException(
"PortletContext resource [" + path + "] cannot be resolved to absolute file path - " +
"web application archive not expanded?");
}
return realPath;
}
/**
* Check the given request for a session attribute of the given name under the
* {@link javax.portlet.PortletSession#PORTLET_SCOPE}.
* Returns <code>null</code> if there is no session or if the session has no such attribute in that scope.
* Does not create a new session if none has existed before!
* @param request current portlet request
* @param name the name of the session attribute
* @return the value of the session attribute, or <code>null</code> if not found
*/
public static Object getSessionAttribute(PortletRequest request, String name) {
return getSessionAttribute(request, name, PortletSession.PORTLET_SCOPE);
}
/**
* Check the given request for a session attribute of the given name in the given scope.
* Returns <code>null</code> if there is no session or if the session has no such attribute in that scope.
* Does not create a new session if none has existed before!
* @param request current portlet request
* @param name the name of the session attribute
* @param scope session scope of this attribute
* @return the value of the session attribute, or <code>null</code> if not found
*/
public static Object getSessionAttribute(PortletRequest request, String name, int scope) {
Assert.notNull(request, "Request must not be null");
PortletSession session = request.getPortletSession(false);
return (session != null ? session.getAttribute(name, scope) : null);
}
/**
* Check the given request for a session attribute of the given name
* under the {@link javax.portlet.PortletSession#PORTLET_SCOPE}.
* Throws an exception if there is no session or if the session has
* no such attribute in that scope.
* <p>Does not create a new session if none has existed before!
* @param request current portlet request
* @param name the name of the session attribute
* @return the value of the session attribute
* @throws IllegalStateException if the session attribute could not be found
*/
public static Object getRequiredSessionAttribute(PortletRequest request, String name)
throws IllegalStateException {
return getRequiredSessionAttribute(request, name, PortletSession.PORTLET_SCOPE);
}
/**
* Check the given request for a session attribute of the given name in the given scope.
* Throws an exception if there is no session or if the session has no such attribute
* in that scope.
* <p>Does not create a new session if none has existed before!
* @param request current portlet request
* @param name the name of the session attribute
* @param scope session scope of this attribute
* @return the value of the session attribute
* @throws IllegalStateException if the session attribute could not be found
*/
public static Object getRequiredSessionAttribute(PortletRequest request, String name, int scope)
throws IllegalStateException {
Object attr = getSessionAttribute(request, name, scope);
if (attr == null) {
throw new IllegalStateException("No session attribute '" + name + "' found");
}
return attr;
}
/**
* Set the session attribute with the given name to the given value under the {@link javax.portlet.PortletSession#PORTLET_SCOPE}.
* Removes the session attribute if value is <code>null</code>, if a session existed at all.
* Does not create a new session if not necessary!
* @param request current portlet request
* @param name the name of the session attribute
* @param value the value of the session attribute
*/
public static void setSessionAttribute(PortletRequest request, String name, Object value) {
setSessionAttribute(request, name, value, PortletSession.PORTLET_SCOPE);
}
/**
* Set the session attribute with the given name to the given value in the given scope.
* Removes the session attribute if value is <code>null</code>, if a session existed at all.
* Does not create a new session if not necessary!
* @param request current portlet request
* @param name the name of the session attribute
* @param value the value of the session attribute
* @param scope session scope of this attribute
*/
public static void setSessionAttribute(PortletRequest request, String name, Object value, int scope) {
Assert.notNull(request, "Request must not be null");
if (value != null) {
request.getPortletSession().setAttribute(name, value, scope);
}
else {
PortletSession session = request.getPortletSession(false);
if (session != null) {
session.removeAttribute(name, scope);
}
}
}
/**
* Get the specified session attribute under the {@link javax.portlet.PortletSession#PORTLET_SCOPE},
* creating and setting a new attribute if no existing found. The given class
* needs to have a public no-arg constructor.
* Useful for on-demand state objects in a web tier, like shopping carts.
* @param session current portlet session
* @param name the name of the session attribute
* @param clazz the class to instantiate for a new attribute
* @return the value of the session attribute, newly created if not found
* @throws IllegalArgumentException if the session attribute could not be instantiated
*/
public static Object getOrCreateSessionAttribute(PortletSession session, String name, Class clazz)
throws IllegalArgumentException {
return getOrCreateSessionAttribute(session, name, clazz, PortletSession.PORTLET_SCOPE);
}
/**
* Get the specified session attribute in the given scope,
* creating and setting a new attribute if no existing found. The given class
* needs to have a public no-arg constructor.
* Useful for on-demand state objects in a web tier, like shopping carts.
* @param session current portlet session
* @param name the name of the session attribute
* @param clazz the class to instantiate for a new attribute
* @param scope the session scope of this attribute
* @return the value of the session attribute, newly created if not found
* @throws IllegalArgumentException if the session attribute could not be instantiated
*/
public static Object getOrCreateSessionAttribute(PortletSession session, String name, Class clazz, int scope)
throws IllegalArgumentException {
Assert.notNull(session, "Session must not be null");
Object sessionObject = session.getAttribute(name, scope);
if (sessionObject == null) {
Assert.notNull(clazz, "Class must not be null if attribute value is to be instantiated");
try {
sessionObject = clazz.newInstance();
}
catch (InstantiationException ex) {
throw new IllegalArgumentException(
"Could not instantiate class [" + clazz.getName() +
"] for session attribute '" + name + "': " + ex.getMessage());
}
catch (IllegalAccessException ex) {
throw new IllegalArgumentException(
"Could not access default constructor of class [" + clazz.getName() +
"] for session attribute '" + name + "': " + ex.getMessage());
}
session.setAttribute(name, sessionObject, scope);
}
return sessionObject;
}
/**
* Return the best available mutex for the given session:
* that is, an object to synchronize on for the given session.
* <p>Returns the session mutex attribute if available; usually,
* this means that the
* {@link org.springframework.web.util.HttpSessionMutexListener}
* needs to be defined in <code>web.xml</code>. Falls back to the
* {@link javax.portlet.PortletSession} itself if no mutex attribute found.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the {@link org.springframework.web.util.WebUtils#SESSION_MUTEX_ATTRIBUTE}
* constant. It serves as a safe reference to synchronize on for locking
* on the current session.
* <p>In many cases, the {@link javax.portlet.PortletSession} reference
* itself is a safe mutex as well, since it will always be the same
* object reference for the same active logical session. However, this is
* not guaranteed across different servlet containers; the only 100% safe
* way is a session mutex.
* @param session the HttpSession to find a mutex for
* @return the mutex object (never <code>null</code>)
* @see org.springframework.web.util.WebUtils#SESSION_MUTEX_ATTRIBUTE
* @see org.springframework.web.util.HttpSessionMutexListener
*/
public static Object getSessionMutex(PortletSession session) {
Assert.notNull(session, "Session must not be null");
Object mutex = session.getAttribute(WebUtils.SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
mutex = session;
}
return mutex;
}
/**
* Expose the given Map as request attributes, using the keys as attribute names
* and the values as corresponding attribute values. Keys must be Strings.
* @param request current portlet request
* @param attributes the attributes Map
*/
public static void exposeRequestAttributes(PortletRequest request, Map attributes) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(attributes, "attributes Map must not be null");
Iterator it = attributes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
if (!(entry.getKey() instanceof String)) {
throw new IllegalArgumentException(
"Invalid key [" + entry.getKey() + "] in attributes Map - only Strings allowed as attribute keys");
}
request.setAttribute((String) entry.getKey(), entry.getValue());
}
}
/**
* Check if a specific input type="submit" parameter was sent in the request,
* either via a button (directly with name) or via an image (name + ".x" or
* name + ".y").
* @param request current portlet request
* @param name name of the parameter
* @return if the parameter was sent
* @see org.springframework.web.util.WebUtils#SUBMIT_IMAGE_SUFFIXES
*/
public static boolean hasSubmitParameter(PortletRequest request, String name) {
return getSubmitParameter(request, name) != null;
}
/**
* Return the full name of a specific input type="submit" parameter
* if it was sent in the request, either via a button (directly with name)
* or via an image (name + ".x" or name + ".y").
* @param request current portlet request
* @param name name of the parameter
* @return the actual parameter name with suffix if needed - null if not present
* @see org.springframework.web.util.WebUtils#SUBMIT_IMAGE_SUFFIXES
*/
public static String getSubmitParameter(PortletRequest request, String name) {
Assert.notNull(request, "Request must not be null");
if (request.getParameter(name) != null) {
return name;
}
for (int i = 0; i < WebUtils.SUBMIT_IMAGE_SUFFIXES.length; i++) {
String suffix = WebUtils.SUBMIT_IMAGE_SUFFIXES[i];
String parameter = name + suffix;
if (request.getParameter(parameter) != null) {
return parameter;
}
}
return null;
}
/**
* Return a map containing all parameters with the given prefix.
* Maps single values to String and multiple values to String array.
* <p>For example, with a prefix of "spring_", "spring_param1" and
* "spring_param2" result in a Map with "param1" and "param2" as keys.
* <p>Similar to portlet
* {@link javax.portlet.PortletRequest#getParameterMap()},
* but more flexible.
* @param request portlet request in which to look for parameters
* @param prefix the beginning of parameter names
* (if this is <code>null</code> or the empty string, all parameters will match)
* @return map containing request parameters <b>without the prefix</b>,
* containing either a String or a String array as values
* @see javax.portlet.PortletRequest#getParameterNames
* @see javax.portlet.PortletRequest#getParameterValues
* @see javax.portlet.PortletRequest#getParameterMap
*/
public static Map getParametersStartingWith(PortletRequest request, String prefix) {
Assert.notNull(request, "Request must not be null");
Enumeration paramNames = request.getParameterNames();
Map params = new TreeMap();
if (prefix == null) {
prefix = "";
}
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
if ("".equals(prefix) || paramName.startsWith(prefix)) {
String unprefixed = paramName.substring(prefix.length());
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
}
else if (values.length > 1) {
params.put(unprefixed, values);
}
else {
params.put(unprefixed, values[0]);
}
}
}
return params;
}
/**
* Return the target page specified in the request.
* @param request current portlet request
* @param paramPrefix the parameter prefix to check for
* (e.g. "_target" for parameters like "_target1" or "_target2")
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
*/
public static int getTargetPage(PortletRequest request, String paramPrefix, int currentPage) {
Enumeration paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
if (paramName.startsWith(paramPrefix)) {
for (int i = 0; i < WebUtils.SUBMIT_IMAGE_SUFFIXES.length; i++) {
String suffix = WebUtils.SUBMIT_IMAGE_SUFFIXES[i];
if (paramName.endsWith(suffix)) {
paramName = paramName.substring(0, paramName.length() - suffix.length());
}
}
return Integer.parseInt(paramName.substring(paramPrefix.length()));
}
}
return currentPage;
}
/**
* Pass all the action request parameters to the render phase by putting them into
* the action response object. This may not be called when the action will call
* {@link javax.portlet.ActionResponse#sendRedirect sendRedirect}.
* @param request the current action request
* @param response the current action response
* @see javax.portlet.ActionResponse#setRenderParameter
*/
public static void passAllParametersToRenderPhase(ActionRequest request, ActionResponse response) {
try {
Enumeration en = request.getParameterNames();
while (en.hasMoreElements()) {
String param = (String) en.nextElement();
String values[] = request.getParameterValues(param);
response.setRenderParameter(param, values);
}
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Clear all the render parameters from the {@link javax.portlet.ActionResponse}.
* This may not be called when the action will call
* {@link ActionResponse#sendRedirect sendRedirect}.
* @param response the current action response
* @see ActionResponse#setRenderParameters
*/
public static void clearAllRenderParameters(ActionResponse response) {
try {
response.setRenderParameters(new HashMap());
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Miscellaneous portlet utility classes.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
The Spring Data Binding framework, an internal library used by Spring Web Flow.
</p>
</body>
</html>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c - %m%n" />
</layout>
</appender>
<logger name="org.springframework.beans">
<level value="warn" />
</logger>
<logger name="org.springframework.binding">
<level value="debug" />
</logger>
<!-- Root Logger -->
<root>
<priority value="warn" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

View File

@ -0,0 +1,24 @@
Bundle-SymbolicName: org.springframework.web.portlet
Bundle-Name: Spring Web Portlet
Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Template:
javax.portlet.*;version="[1.0.0, 2.0.0)",
javax.servlet.*;version="[2.4.0, 3.0.0)",
org.apache.commons.fileupload.*;version="[1.2.0, 2.0.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
org.springframework.beans.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.context.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.core.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.stereotype;version="[2.5.5.A, 2.5.5.A]",
org.springframework.ui.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.util.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.validation.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.web.*;version="[2.5.5.A, 2.5.5.A]"
Unversioned-Imports:
org.xml.sax.*
Ignored-Existing-Headers:
Bnd-LastModified,
Import-Package,
Export-Package,
Tool