diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/EventMapping.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/EventMapping.java
index cbd3086d662..c6c605dc824 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/EventMapping.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/EventMapping.java
@@ -34,30 +34,19 @@ import org.springframework.web.bind.annotation.Mapping;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
-@Mapping
+@Mapping()
public @interface EventMapping {
/**
* The name of the event to be handled.
+ * This name uniquely identifies an event within a portlet mode.
*
Typically the local name of the event, but fully qualified names
* with a "{...}" namespace part will be mapped correctly as well.
- *
If not specified, the render method will be invoked for any
+ *
If not specified, the handler method will be invoked for any
* event request within its general mapping.
* @see javax.portlet.EventRequest#getEvent()
* @see javax.portlet.Event#getName()
*/
- String value();
-
- /**
- * The parameters of the mapped request, narrowing the primary mapping.
- *
Same format for any environment: a sequence of "myParam=myValue" style
- * expressions, with a request only mapped if each such parameter is found
- * to have the given value. "myParam" style expressions are also supported,
- * with such parameters having to be present in the request (allowed to have
- * any value). Finally, "!myParam" style expressions indicate that the
- * specified parameter is not supposed to be present in the request.
- * @see org.springframework.web.bind.annotation.RequestMapping#params()
- */
- String[] params() default {};
+ String value() default "";
}
diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/ResourceMapping.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/ResourceMapping.java
index de9ac2836fc..250d8e7faa7 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/ResourceMapping.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/annotation/ResourceMapping.java
@@ -34,27 +34,16 @@ import org.springframework.web.bind.annotation.Mapping;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
-@Mapping
+@Mapping()
public @interface ResourceMapping {
/**
* The id of the resource to be handled.
- *
If not specified, the render method will be invoked for any
+ * This id uniquely identifies a resource within a portlet mode.
+ *
If not specified, the handler method will be invoked for any
* resource request within its general mapping.
* @see javax.portlet.ResourceRequest#getResourceID()
*/
String value() default "";
- /**
- * The parameters of the mapped request, narrowing the primary mapping.
- *
Same format for any environment: a sequence of "myParam=myValue" style
- * expressions, with a request only mapped if each such parameter is found
- * to have the given value. "myParam" style expressions are also supported,
- * with such parameters having to be present in the request (allowed to have
- * any value). Finally, "!myParam" style expressions indicate that the
- * specified parameter is not supposed to be present in the request.
- * @see org.springframework.web.bind.annotation.RequestMapping#params()
- */
- String[] params() default {};
-
}
diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index aeeffa92a0e..f6b7c420436 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -385,13 +385,14 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
mappingInfo.initPhaseMapping(PortletRequest.RENDER_PHASE, renderMapping.value(), renderMapping.params());
}
if (resourceMapping != null) {
- mappingInfo.initPhaseMapping(PortletRequest.RESOURCE_PHASE, resourceMapping.value(), resourceMapping.params());
+ mappingInfo.initPhaseMapping(PortletRequest.RESOURCE_PHASE, resourceMapping.value(), new String[0]);
}
if (eventMapping != null) {
- mappingInfo.initPhaseMapping(PortletRequest.EVENT_PHASE, eventMapping.value(), eventMapping.params());
+ mappingInfo.initPhaseMapping(PortletRequest.EVENT_PHASE, eventMapping.value(), new String[0]);
}
if (requestMapping != null) {
- mappingInfo.initStandardMapping(requestMapping.value(), requestMapping.method(), requestMapping.params());
+ mappingInfo.initStandardMapping(requestMapping.value(), requestMapping.method(),
+ requestMapping.params(), requestMapping.headers());
if (mappingInfo.phase == null) {
mappingInfo.phase = determineDefaultPhase(method);
}
@@ -466,19 +467,21 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
}
- private static class RequestMappingInfo {
+ private static class RequestMappingInfo {
- public Set modes = new HashSet();
+ public final Set modes = new HashSet();
public String phase;
public String value;
- public Set methods = new HashSet();
+ public final Set methods = new HashSet();
public String[] params = new String[0];
- public void initStandardMapping(String[] modes, RequestMethod[] methods, String[] params) {
+ public String[] headers = new String[0];
+
+ public void initStandardMapping(String[] modes, RequestMethod[] methods, String[] params, String[] headers) {
for (String mode : modes) {
this.modes.add(new PortletMode(mode));
}
@@ -486,6 +489,7 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
this.methods.add(method.name());
}
this.params = StringUtils.mergeStringArrays(this.params, params);
+ this.headers = StringUtils.mergeStringArrays(this.headers, headers);
}
public void initPhaseMapping(String phase, String value, String[] params) {
@@ -527,7 +531,8 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
}
}
return PortletAnnotationMappingUtils.checkRequestMethod(this.methods, request) &&
- PortletAnnotationMappingUtils.checkParameters(this.params, request);
+ PortletAnnotationMappingUtils.checkParameters(this.params, request) &&
+ PortletAnnotationMappingUtils.checkHeaders(this.headers, request);
}
public boolean isBetterMatchThan(RequestMappingInfo other) {
@@ -545,7 +550,8 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
ObjectUtils.nullSafeEquals(this.phase, other.phase) &&
ObjectUtils.nullSafeEquals(this.value, other.value) &&
this.methods.equals(other.methods) &&
- Arrays.equals(this.params, other.params));
+ Arrays.equals(this.params, other.params) &&
+ Arrays.equals(this.headers, other.headers));
}
@Override
diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java
index 473eb505264..11a40405257 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java
@@ -18,12 +18,18 @@ package org.springframework.web.portlet.mvc.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
import javax.portlet.ClientDataRequest;
+import javax.portlet.Event;
+import javax.portlet.EventRequest;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
+import javax.portlet.ResourceRequest;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@@ -34,6 +40,9 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.portlet.bind.PortletRequestBindingException;
+import org.springframework.web.portlet.bind.annotation.EventMapping;
+import org.springframework.web.portlet.bind.annotation.ResourceMapping;
import org.springframework.web.portlet.handler.AbstractMapBasedHandlerMapping;
import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedException;
@@ -64,10 +73,10 @@ import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedE
* at the method level.
*
* NOTE: 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.
+ * expressed at the class level (if any). A portlet mode in combination with specific
+ * parameter conditions needs to uniquely map 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.
*
*
The {@link AnnotationMethodHandlerAdapter} is responsible for processing
* annotated handler methods, as mapped by this HandlerMapping. For
@@ -81,6 +90,9 @@ import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedE
*/
public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapping {
+ private final Map cachedMappings = new HashMap();
+
+
/**
* Calls the registerHandlers method in addition
* to the superclass's initialization.
@@ -103,15 +115,17 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
Class> handlerType = context.getType(beanName);
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
if (mapping != null) {
+ // @RequestMapping found at type level
+ this.cachedMappings.put(handlerType, mapping);
String[] modeKeys = mapping.value();
String[] params = mapping.params();
- RequestMethod[] methods = mapping.method();
boolean registerHandlerType = true;
if (modeKeys.length == 0 || params.length == 0) {
registerHandlerType = !detectHandlerMethods(handlerType, beanName, mapping);
}
if (registerHandlerType) {
- ParameterMappingPredicate predicate = new ParameterMappingPredicate(params, methods);
+ ParameterMappingPredicate predicate = new ParameterMappingPredicate(
+ params, mapping.headers(), mapping.method());
for (String modeKey : modeKeys) {
registerHandler(new PortletMode(modeKey), beanName, predicate);
}
@@ -138,14 +152,28 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
boolean mappingFound = false;
String[] modeKeys = new String[0];
String[] params = new String[0];
+ String resourceId = null;
+ String eventName = null;
for (Annotation ann : method.getAnnotations()) {
if (AnnotationUtils.findAnnotation(ann.getClass(), Mapping.class) != null) {
mappingFound = true;
if (ann instanceof RequestMapping) {
- modeKeys = (String[]) AnnotationUtils.getValue(ann);
+ RequestMapping rm = (RequestMapping) ann;
+ modeKeys = rm.value();
+ params = StringUtils.mergeStringArrays(params, rm.params());
+ }
+ else if (ann instanceof ResourceMapping) {
+ ResourceMapping rm = (ResourceMapping) ann;
+ resourceId = rm.value();
+ }
+ else if (ann instanceof EventMapping) {
+ EventMapping em = (EventMapping) ann;
+ eventName = em.value();
+ }
+ else {
+ String[] specificParams = (String[]) AnnotationUtils.getValue(ann, "params");
+ params = StringUtils.mergeStringArrays(params, specificParams);
}
- String[] specificParams = (String[]) AnnotationUtils.getValue(ann, "params");
- params = StringUtils.mergeStringArrays(params, specificParams);
}
}
if (mappingFound) {
@@ -155,14 +183,26 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
}
else {
throw new IllegalStateException(
- "No portlet mode mappings specified - neither at type nor method level");
+ "No portlet mode mappings specified - neither at type nor at method level");
}
}
if (typeMapping != null) {
- PortletAnnotationMappingUtils.validateModeMapping(modeKeys, typeMapping.value());
+ if (!PortletAnnotationMappingUtils.validateModeMapping(modeKeys, typeMapping.value())) {
+ throw new IllegalStateException("Mode mappings conflict between method and type level: " +
+ Arrays.asList(modeKeys) + " versus " + Arrays.asList(typeMapping.value()));
+ }
params = StringUtils.mergeStringArrays(typeMapping.params(), params);
}
- ParameterMappingPredicate predicate = new ParameterMappingPredicate(params);
+ PortletRequestMappingPredicate predicate;
+ if (resourceId != null) {
+ predicate = new ResourceMappingPredicate(resourceId);
+ }
+ else if (eventName != null) {
+ predicate = new EventMappingPredicate(eventName);
+ }
+ else {
+ predicate = new ParameterMappingPredicate(params);
+ }
for (String modeKey : modeKeys) {
registerHandler(new PortletMode(modeKey), beanName, predicate);
handlersRegistered.add(Boolean.TRUE);
@@ -181,6 +221,50 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
return request.getPortletMode();
}
+ /**
+ * Validate the given annotated handler against the current request.
+ * @see #validateMapping
+ */
+ protected void validateHandler(Object handler, PortletRequest request) throws Exception {
+ RequestMapping mapping = this.cachedMappings.get(handler.getClass());
+ if (mapping == null) {
+ mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class);
+ }
+ if (mapping != null) {
+ validateMapping(mapping, request);
+ }
+ }
+
+ /**
+ * Validate the given type-level mapping metadata against the current request,
+ * checking HTTP request method and parameter conditions.
+ * @param mapping the mapping metadata to validate
+ * @param request current HTTP request
+ * @throws Exception if validation failed
+ */
+ protected void validateMapping(RequestMapping mapping, PortletRequest request) throws Exception {
+ RequestMethod[] mappedMethods = mapping.method();
+ if (!PortletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) {
+ String[] supportedMethods = new String[mappedMethods.length];
+ for (int i = 0; i < mappedMethods.length; i++) {
+ supportedMethods[i] = mappedMethods[i].name();
+ }
+ if (request instanceof ClientDataRequest) {
+ throw new PortletRequestMethodNotSupportedException(((ClientDataRequest) request).getMethod(), supportedMethods);
+ }
+ else {
+ throw new PortletRequestMethodNotSupportedException(supportedMethods);
+ }
+ }
+
+ String[] mappedHeaders = mapping.headers();
+ if (!PortletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
+ throw new PortletRequestBindingException("Header conditions \"" +
+ StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
+ "\" not met for actual request");
+ }
+ }
+
/**
* Predicate that matches against parameter conditions.
@@ -189,16 +273,21 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
private final String[] params;
+ private final String[] headers;
+
private final Set methods = new HashSet();
public ParameterMappingPredicate(String[] params) {
- this.params = params;
+ this(params, null, null);
}
- public ParameterMappingPredicate(String[] params, RequestMethod[] methods) {
+ public ParameterMappingPredicate(String[] params, String[] headers, RequestMethod[] methods) {
this.params = params;
- for (RequestMethod method : methods) {
- this.methods.add(method.name());
+ this.headers = headers;
+ if (methods != null) {
+ for (RequestMethod method : methods) {
+ this.methods.add(method.name());
+ }
}
}
@@ -207,6 +296,12 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
}
public void validate(PortletRequest request) throws PortletException {
+ if (!PortletAnnotationMappingUtils.checkHeaders(this.headers, request)) {
+ throw new PortletRequestBindingException("Header conditions \"" +
+ StringUtils.arrayToDelimitedString(this.headers, ", ") +
+ "\" not met for actual request");
+ }
+
if (!this.methods.isEmpty()) {
if (!(request instanceof ClientDataRequest)) {
throw new PortletRequestMethodNotSupportedException(StringUtils.toStringArray(this.methods));
@@ -219,11 +314,11 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
}
public int compareTo(Object other) {
- if (other instanceof PortletRequestMappingPredicate) {
+ if (other instanceof ParameterMappingPredicate) {
return new Integer(((ParameterMappingPredicate) other).params.length).compareTo(this.params.length);
}
else {
- return 0;
+ return 1;
}
}
@@ -233,4 +328,54 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
}
}
+
+ private static class ResourceMappingPredicate implements PortletRequestMappingPredicate {
+
+ private final String resourceId;
+
+ public ResourceMappingPredicate(String resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ public boolean match(PortletRequest request) {
+ return (PortletRequest.RESOURCE_PHASE.equals(request.getAttribute(PortletRequest.LIFECYCLE_PHASE)) &&
+ ("".equals(this.resourceId) || this.resourceId.equals(((ResourceRequest) request).getResourceID())));
+ }
+
+ public void validate(PortletRequest request) {
+ }
+
+ public int compareTo(Object o) {
+ return -1;
+ }
+ }
+
+
+ private static class EventMappingPredicate implements PortletRequestMappingPredicate {
+
+ private final String eventName;
+
+ public EventMappingPredicate(String eventName) {
+ this.eventName = eventName;
+ }
+
+ public boolean match(PortletRequest request) {
+ if (!PortletRequest.EVENT_PHASE.equals(request.getAttribute(PortletRequest.LIFECYCLE_PHASE))) {
+ return false;
+ }
+ if ("".equals(this.eventName)) {
+ return true;
+ }
+ Event event = ((EventRequest) request).getEvent();
+ return (this.eventName.equals(event.getName()) || this.eventName.equals(event.getQName().toString()));
+ }
+
+ public void validate(PortletRequest request) {
+ }
+
+ public int compareTo(Object o) {
+ return -1;
+ }
+ }
+
}
diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/PortletAnnotationMappingUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/PortletAnnotationMappingUtils.java
index 71c1db87d1a..f1796355cf4 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/PortletAnnotationMappingUtils.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/PortletAnnotationMappingUtils.java
@@ -16,11 +16,15 @@
package org.springframework.web.portlet.mvc.annotation;
+import java.util.Iterator;
+import java.util.List;
import java.util.Set;
import javax.portlet.ClientDataRequest;
import javax.portlet.PortletRequest;
+import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.portlet.util.PortletUtils;
/**
@@ -37,7 +41,7 @@ abstract class PortletAnnotationMappingUtils {
* @param typeLevelModes the type-level mode mappings to check against
*/
public static boolean validateModeMapping(String[] modes, String[] typeLevelModes) {
- if (!ObjectUtils.isEmpty(modes)) {
+ if (!ObjectUtils.isEmpty(modes) && !ObjectUtils.isEmpty(typeLevelModes)) {
for (String mode : modes) {
boolean match = false;
for (String typeLevelMode : typeLevelModes) {
@@ -53,6 +57,27 @@ abstract class PortletAnnotationMappingUtils {
return true;
}
+ /**
+ * Check whether the given request matches the specified request methods.
+ * @param methods the request methods to check against
+ * @param request the current request to check
+ */
+ public static boolean checkRequestMethod(RequestMethod[] methods, PortletRequest request) {
+ if (methods.length == 0) {
+ return true;
+ }
+ if (!(request instanceof ClientDataRequest)) {
+ return false;
+ }
+ String method = ((ClientDataRequest) request).getMethod();
+ for (RequestMethod candidate : methods) {
+ if (method.equals(candidate.name())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Check whether the given request matches the specified request methods.
* @param methods the request methods to check against
@@ -103,4 +128,57 @@ abstract class PortletAnnotationMappingUtils {
return true;
}
+ /**
+ * Check whether the given request matches the specified header conditions.
+ * @param headers the header conditions, following {@link RequestMapping#headers()}
+ * @param request the current HTTP request to check
+ */
+ public static boolean checkHeaders(String[] headers, PortletRequest request) {
+ if (!ObjectUtils.isEmpty(headers)) {
+ for (String header : headers) {
+ int separator = header.indexOf('=');
+ if (separator == -1) {
+ if (header.startsWith("!")) {
+ if (request.getProperty(header.substring(1)) != null) {
+ return false;
+ }
+ }
+ else if (request.getProperty(header) == null) {
+ return false;
+ }
+ }
+ else {
+ String key = header.substring(0, separator);
+ String value = header.substring(separator + 1);
+ if (isMediaTypeHeader(key)) {
+ List requestMediaTypes = MediaType.parseMediaTypes(request.getProperty(key));
+ List valueMediaTypes = MediaType.parseMediaTypes(value);
+ boolean found = false;
+ for (Iterator valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
+ MediaType valueMediaType = valIter.next();
+ for (Iterator reqIter = requestMediaTypes.iterator(); reqIter.hasNext() && !found;) {
+ MediaType requestMediaType = reqIter.next();
+ if (valueMediaType.includes(requestMediaType)) {
+ found = true;
+ }
+ }
+
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ else if (!value.equals(request.getProperty(key))) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean isMediaTypeHeader(String headerName) {
+ return "Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName);
+ }
+
}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java
index 306529ceca9..8749eb88e8d 100644
--- a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java
@@ -474,6 +474,7 @@ public class Portlet20AnnotationControllerTests {
RootBeanDefinition bd = new RootBeanDefinition(MyPortlet20DispatchingController.class);
bd.setScope(WebApplicationContext.SCOPE_REQUEST);
wac.registerBeanDefinition("controller", bd);
+ wac.registerBeanDefinition("controller2", new RootBeanDefinition(DetailsController.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(wac);
wac.refresh();
return wac;
@@ -543,6 +544,88 @@ public class Portlet20AnnotationControllerTests {
assertEquals("myDefaultResource", resourceResponse.getContentAsString());
}
+ @Test
+ public void eventDispatchingController() throws Exception {
+ DispatcherPortlet portlet = new DispatcherPortlet() {
+ protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException {
+ StaticPortletApplicationContext wac = new StaticPortletApplicationContext();
+ wac.setPortletContext(new MockPortletContext());
+ RootBeanDefinition bd = new RootBeanDefinition(MyPortlet20DispatchingController.class);
+ bd.setScope(WebApplicationContext.SCOPE_REQUEST);
+ wac.registerBeanDefinition("controller", bd);
+ wac.registerBeanDefinition("controller2", new RootBeanDefinition(DetailsController.class));
+ AnnotationConfigUtils.registerAnnotationConfigProcessors(wac);
+ wac.refresh();
+ return wac;
+ }
+ };
+ portlet.init(new MockPortletConfig());
+
+ MockRenderRequest request = new MockRenderRequest();
+ MockRenderResponse response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myView", response.getContentAsString());
+
+ MockActionRequest actionRequest = new MockActionRequest("this");
+ MockActionResponse actionResponse = new MockActionResponse();
+ portlet.processAction(actionRequest, actionResponse);
+
+ request = new MockRenderRequest(PortletMode.VIEW, WindowState.MAXIMIZED);
+ request.setParameters(actionResponse.getRenderParameterMap());
+ response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myLargeView-value", response.getContentAsString());
+
+ actionRequest = new MockActionRequest();
+ actionRequest.addParameter("action", "details");
+ actionResponse = new MockActionResponse();
+ portlet.processAction(actionRequest, actionResponse);
+
+ request = new MockRenderRequest(PortletMode.VIEW, WindowState.MAXIMIZED);
+ request.setParameters(actionResponse.getRenderParameterMap());
+ response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myLargeView-details", response.getContentAsString());
+
+ MockEventRequest eventRequest = new MockEventRequest(new MockEvent("event1"));
+ eventRequest.setParameters(actionRequest.getParameterMap());
+ MockEventResponse eventResponse = new MockEventResponse();
+ portlet.processEvent(eventRequest, eventResponse);
+
+ request = new MockRenderRequest(PortletMode.VIEW, WindowState.MAXIMIZED);
+ request.setParameters(eventResponse.getRenderParameterMap());
+ response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myLargeView-value3", response.getContentAsString());
+
+ eventRequest = new MockEventRequest(new MockEvent("event3"));
+ eventRequest.setParameters(actionRequest.getParameterMap());
+ eventResponse = new MockEventResponse();
+ portlet.processEvent(eventRequest, eventResponse);
+
+ request = new MockRenderRequest(PortletMode.VIEW, WindowState.MAXIMIZED);
+ request.setParameters(eventResponse.getRenderParameterMap());
+ response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myLargeView-value4", response.getContentAsString());
+
+ request = new MockRenderRequest(PortletMode.VIEW, WindowState.NORMAL);
+ request.setParameters(actionResponse.getRenderParameterMap());
+ response = new MockRenderResponse();
+ portlet.render(request, response);
+ assertEquals("myView", response.getContentAsString());
+
+ MockResourceRequest resourceRequest = new MockResourceRequest("resource1");
+ MockResourceResponse resourceResponse = new MockResourceResponse();
+ portlet.serveResource(resourceRequest, resourceResponse);
+ assertEquals("myResource", resourceResponse.getContentAsString());
+
+ resourceRequest = new MockResourceRequest("resource2");
+ resourceResponse = new MockResourceResponse();
+ portlet.serveResource(resourceRequest, resourceResponse);
+ assertEquals("myDefaultResource", resourceResponse.getContentAsString());
+ }
+
/*
* Controllers
@@ -916,6 +999,22 @@ public class Portlet20AnnotationControllerTests {
public void myResource(Writer writer) throws IOException {
writer.write("myResource");
}
+ }
+
+
+ @Controller
+ @RequestMapping("VIEW")
+ private static class DetailsController {
+
+ @EventMapping("event3")
+ public void myHandle2(EventResponse response) throws IOException {
+ response.setRenderParameter("test", "value4");
+ }
+
+ @ActionMapping(params = "action=details")
+ public void renderDetails(ActionRequest request, ActionResponse response, Model model) {
+ response.setRenderParameter("test", "details");
+ }
@ResourceMapping
public void myDefaultResource(Writer writer) throws IOException {
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 55db587c9b3..d3a77d23866 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -28,11 +28,13 @@ import java.lang.annotation.Target;
* environments, with the semantics adapting to the concrete environment.
*
* NOTE: Method-level mappings are only allowed to narrow the mapping
- * expressed at the class level (if any). HTTP paths / portlet modes need to
- * uniquely map onto specific handler beans, with any given path / 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.
+ * expressed at the class level (if any). In the Servlet case, an HTTP path needs to
+ * uniquely map onto one specific handler bean (not spread across multiple handler beans);
+ * the remaining mapping parameters and conditions are effectively assertions only.
+ * In the Portlet case, a portlet mode in combination with specific parameter conditions
+ * needs to uniquely map onto one specific handler bean, with all conditions evaluated
+ * for mapping purposes. It is strongly recommended to co-locate related handler methods
+ * into the same bean and therefore keep the mappings simple and intuitive.
*
*
Handler methods which are annotated with this annotation are allowed
* to have very flexible signatures. They may have arguments of the following
@@ -256,7 +258,10 @@ public @interface RequestMapping {
* When used at the type level, all method-level mappings inherit
* this header restriction (i.e. the type-level restriction
* gets checked before the handler method is even resolved).
+ *
Maps against HttpServletRequest headers in a Servlet environment,
+ * and against PortletRequest properties in a Portlet 2.0 environment.
* @see org.springframework.http.MediaType
*/
String[] headers() default {};
+
}