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 {}; + }