@Event/ResourceMapping uniquely mapped to through event/resource id, even across controllers (SPR-6062); type-level @RequestMapping header conditions validated in Portlet environments as well
This commit is contained in:
parent
76122c931d
commit
4d29f65a9c
|
|
@ -34,30 +34,19 @@ import org.springframework.web.bind.annotation.Mapping;
|
||||||
@Target({ElementType.METHOD})
|
@Target({ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@Mapping
|
@Mapping()
|
||||||
public @interface EventMapping {
|
public @interface EventMapping {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the event to be handled.
|
* The name of the event to be handled.
|
||||||
|
* This name uniquely identifies an event within a portlet mode.
|
||||||
* <p>Typically the local name of the event, but fully qualified names
|
* <p>Typically the local name of the event, but fully qualified names
|
||||||
* with a "{...}" namespace part will be mapped correctly as well.
|
* with a "{...}" namespace part will be mapped correctly as well.
|
||||||
* <p>If not specified, the render method will be invoked for any
|
* <p>If not specified, the handler method will be invoked for any
|
||||||
* event request within its general mapping.
|
* event request within its general mapping.
|
||||||
* @see javax.portlet.EventRequest#getEvent()
|
* @see javax.portlet.EventRequest#getEvent()
|
||||||
* @see javax.portlet.Event#getName()
|
* @see javax.portlet.Event#getName()
|
||||||
*/
|
*/
|
||||||
String value();
|
String value() default "";
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameters of the mapped request, narrowing the primary mapping.
|
|
||||||
* <p>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 <i>not</i> supposed to be present in the request.
|
|
||||||
* @see org.springframework.web.bind.annotation.RequestMapping#params()
|
|
||||||
*/
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,27 +34,16 @@ import org.springframework.web.bind.annotation.Mapping;
|
||||||
@Target({ElementType.METHOD})
|
@Target({ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@Mapping
|
@Mapping()
|
||||||
public @interface ResourceMapping {
|
public @interface ResourceMapping {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the resource to be handled.
|
* The id of the resource to be handled.
|
||||||
* <p>If not specified, the render method will be invoked for any
|
* This id uniquely identifies a resource within a portlet mode.
|
||||||
|
* <p>If not specified, the handler method will be invoked for any
|
||||||
* resource request within its general mapping.
|
* resource request within its general mapping.
|
||||||
* @see javax.portlet.ResourceRequest#getResourceID()
|
* @see javax.portlet.ResourceRequest#getResourceID()
|
||||||
*/
|
*/
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameters of the mapped request, narrowing the primary mapping.
|
|
||||||
* <p>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 <i>not</i> supposed to be present in the request.
|
|
||||||
* @see org.springframework.web.bind.annotation.RequestMapping#params()
|
|
||||||
*/
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -385,13 +385,14 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
|
||||||
mappingInfo.initPhaseMapping(PortletRequest.RENDER_PHASE, renderMapping.value(), renderMapping.params());
|
mappingInfo.initPhaseMapping(PortletRequest.RENDER_PHASE, renderMapping.value(), renderMapping.params());
|
||||||
}
|
}
|
||||||
if (resourceMapping != null) {
|
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) {
|
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) {
|
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) {
|
if (mappingInfo.phase == null) {
|
||||||
mappingInfo.phase = determineDefaultPhase(method);
|
mappingInfo.phase = determineDefaultPhase(method);
|
||||||
}
|
}
|
||||||
|
|
@ -466,19 +467,21 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class RequestMappingInfo {
|
private static class RequestMappingInfo {
|
||||||
|
|
||||||
public Set<PortletMode> modes = new HashSet<PortletMode>();
|
public final Set<PortletMode> modes = new HashSet<PortletMode>();
|
||||||
|
|
||||||
public String phase;
|
public String phase;
|
||||||
|
|
||||||
public String value;
|
public String value;
|
||||||
|
|
||||||
public Set<String> methods = new HashSet<String>();
|
public final Set<String> methods = new HashSet<String>();
|
||||||
|
|
||||||
public String[] params = new String[0];
|
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) {
|
for (String mode : modes) {
|
||||||
this.modes.add(new PortletMode(mode));
|
this.modes.add(new PortletMode(mode));
|
||||||
}
|
}
|
||||||
|
|
@ -486,6 +489,7 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
|
||||||
this.methods.add(method.name());
|
this.methods.add(method.name());
|
||||||
}
|
}
|
||||||
this.params = StringUtils.mergeStringArrays(this.params, params);
|
this.params = StringUtils.mergeStringArrays(this.params, params);
|
||||||
|
this.headers = StringUtils.mergeStringArrays(this.headers, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initPhaseMapping(String phase, String value, String[] params) {
|
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) &&
|
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) {
|
public boolean isBetterMatchThan(RequestMappingInfo other) {
|
||||||
|
|
@ -545,7 +550,8 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
|
||||||
ObjectUtils.nullSafeEquals(this.phase, other.phase) &&
|
ObjectUtils.nullSafeEquals(this.phase, other.phase) &&
|
||||||
ObjectUtils.nullSafeEquals(this.value, other.value) &&
|
ObjectUtils.nullSafeEquals(this.value, other.value) &&
|
||||||
this.methods.equals(other.methods) &&
|
this.methods.equals(other.methods) &&
|
||||||
Arrays.equals(this.params, other.params));
|
Arrays.equals(this.params, other.params) &&
|
||||||
|
Arrays.equals(this.headers, other.headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,18 @@ package org.springframework.web.portlet.mvc.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.portlet.ClientDataRequest;
|
import javax.portlet.ClientDataRequest;
|
||||||
|
import javax.portlet.Event;
|
||||||
|
import javax.portlet.EventRequest;
|
||||||
import javax.portlet.PortletException;
|
import javax.portlet.PortletException;
|
||||||
import javax.portlet.PortletMode;
|
import javax.portlet.PortletMode;
|
||||||
import javax.portlet.PortletRequest;
|
import javax.portlet.PortletRequest;
|
||||||
|
import javax.portlet.ResourceRequest;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
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.Mapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
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.AbstractMapBasedHandlerMapping;
|
||||||
import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedException;
|
import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedException;
|
||||||
|
|
||||||
|
|
@ -64,10 +73,10 @@ import org.springframework.web.portlet.handler.PortletRequestMethodNotSupportedE
|
||||||
* at the method level.
|
* at the method level.
|
||||||
*
|
*
|
||||||
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
|
* <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
|
* expressed at the class level (if any). A portlet mode in combination with specific
|
||||||
* specific handler beans, with any given portlet mode only allowed to be mapped
|
* parameter conditions needs to uniquely map onto one specific handler bean,
|
||||||
* onto one specific handler bean (not spread across multiple handler beans).
|
* not spread across multiple handler beans. It is strongly recommended to
|
||||||
* It is strongly recommended to co-locate related handler methods into the same bean.
|
* co-locate related handler methods into the same bean.
|
||||||
*
|
*
|
||||||
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
|
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
|
||||||
* annotated handler methods, as mapped by this HandlerMapping. For
|
* 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<PortletMode> {
|
public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapping<PortletMode> {
|
||||||
|
|
||||||
|
private final Map<Class, RequestMapping> cachedMappings = new HashMap<Class, RequestMapping>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the <code>registerHandlers</code> method in addition
|
* Calls the <code>registerHandlers</code> method in addition
|
||||||
* to the superclass's initialization.
|
* to the superclass's initialization.
|
||||||
|
|
@ -103,15 +115,17 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
Class<?> handlerType = context.getType(beanName);
|
Class<?> handlerType = context.getType(beanName);
|
||||||
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
|
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
|
||||||
if (mapping != null) {
|
if (mapping != null) {
|
||||||
|
// @RequestMapping found at type level
|
||||||
|
this.cachedMappings.put(handlerType, mapping);
|
||||||
String[] modeKeys = mapping.value();
|
String[] modeKeys = mapping.value();
|
||||||
String[] params = mapping.params();
|
String[] params = mapping.params();
|
||||||
RequestMethod[] methods = mapping.method();
|
|
||||||
boolean registerHandlerType = true;
|
boolean registerHandlerType = true;
|
||||||
if (modeKeys.length == 0 || params.length == 0) {
|
if (modeKeys.length == 0 || params.length == 0) {
|
||||||
registerHandlerType = !detectHandlerMethods(handlerType, beanName, mapping);
|
registerHandlerType = !detectHandlerMethods(handlerType, beanName, mapping);
|
||||||
}
|
}
|
||||||
if (registerHandlerType) {
|
if (registerHandlerType) {
|
||||||
ParameterMappingPredicate predicate = new ParameterMappingPredicate(params, methods);
|
ParameterMappingPredicate predicate = new ParameterMappingPredicate(
|
||||||
|
params, mapping.headers(), mapping.method());
|
||||||
for (String modeKey : modeKeys) {
|
for (String modeKey : modeKeys) {
|
||||||
registerHandler(new PortletMode(modeKey), beanName, predicate);
|
registerHandler(new PortletMode(modeKey), beanName, predicate);
|
||||||
}
|
}
|
||||||
|
|
@ -138,14 +152,28 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
boolean mappingFound = false;
|
boolean mappingFound = false;
|
||||||
String[] modeKeys = new String[0];
|
String[] modeKeys = new String[0];
|
||||||
String[] params = new String[0];
|
String[] params = new String[0];
|
||||||
|
String resourceId = null;
|
||||||
|
String eventName = null;
|
||||||
for (Annotation ann : method.getAnnotations()) {
|
for (Annotation ann : method.getAnnotations()) {
|
||||||
if (AnnotationUtils.findAnnotation(ann.getClass(), Mapping.class) != null) {
|
if (AnnotationUtils.findAnnotation(ann.getClass(), Mapping.class) != null) {
|
||||||
mappingFound = true;
|
mappingFound = true;
|
||||||
if (ann instanceof RequestMapping) {
|
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) {
|
if (mappingFound) {
|
||||||
|
|
@ -155,14 +183,26 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalStateException(
|
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) {
|
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);
|
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) {
|
for (String modeKey : modeKeys) {
|
||||||
registerHandler(new PortletMode(modeKey), beanName, predicate);
|
registerHandler(new PortletMode(modeKey), beanName, predicate);
|
||||||
handlersRegistered.add(Boolean.TRUE);
|
handlersRegistered.add(Boolean.TRUE);
|
||||||
|
|
@ -181,6 +221,50 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
return request.getPortletMode();
|
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.
|
* Predicate that matches against parameter conditions.
|
||||||
|
|
@ -189,16 +273,21 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
|
|
||||||
private final String[] params;
|
private final String[] params;
|
||||||
|
|
||||||
|
private final String[] headers;
|
||||||
|
|
||||||
private final Set<String> methods = new HashSet<String>();
|
private final Set<String> methods = new HashSet<String>();
|
||||||
|
|
||||||
public ParameterMappingPredicate(String[] params) {
|
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;
|
this.params = params;
|
||||||
for (RequestMethod method : methods) {
|
this.headers = headers;
|
||||||
this.methods.add(method.name());
|
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 {
|
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 (!this.methods.isEmpty()) {
|
||||||
if (!(request instanceof ClientDataRequest)) {
|
if (!(request instanceof ClientDataRequest)) {
|
||||||
throw new PortletRequestMethodNotSupportedException(StringUtils.toStringArray(this.methods));
|
throw new PortletRequestMethodNotSupportedException(StringUtils.toStringArray(this.methods));
|
||||||
|
|
@ -219,11 +314,11 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareTo(Object other) {
|
public int compareTo(Object other) {
|
||||||
if (other instanceof PortletRequestMappingPredicate) {
|
if (other instanceof ParameterMappingPredicate) {
|
||||||
return new Integer(((ParameterMappingPredicate) other).params.length).compareTo(this.params.length);
|
return new Integer(((ParameterMappingPredicate) other).params.length).compareTo(this.params.length);
|
||||||
}
|
}
|
||||||
else {
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,15 @@
|
||||||
|
|
||||||
package org.springframework.web.portlet.mvc.annotation;
|
package org.springframework.web.portlet.mvc.annotation;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.portlet.ClientDataRequest;
|
import javax.portlet.ClientDataRequest;
|
||||||
import javax.portlet.PortletRequest;
|
import javax.portlet.PortletRequest;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.portlet.util.PortletUtils;
|
import org.springframework.web.portlet.util.PortletUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,7 +41,7 @@ abstract class PortletAnnotationMappingUtils {
|
||||||
* @param typeLevelModes the type-level mode mappings to check against
|
* @param typeLevelModes the type-level mode mappings to check against
|
||||||
*/
|
*/
|
||||||
public static boolean validateModeMapping(String[] modes, String[] typeLevelModes) {
|
public static boolean validateModeMapping(String[] modes, String[] typeLevelModes) {
|
||||||
if (!ObjectUtils.isEmpty(modes)) {
|
if (!ObjectUtils.isEmpty(modes) && !ObjectUtils.isEmpty(typeLevelModes)) {
|
||||||
for (String mode : modes) {
|
for (String mode : modes) {
|
||||||
boolean match = false;
|
boolean match = false;
|
||||||
for (String typeLevelMode : typeLevelModes) {
|
for (String typeLevelMode : typeLevelModes) {
|
||||||
|
|
@ -53,6 +57,27 @@ abstract class PortletAnnotationMappingUtils {
|
||||||
return true;
|
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.
|
* Check whether the given request matches the specified request methods.
|
||||||
* @param methods the request methods to check against
|
* @param methods the request methods to check against
|
||||||
|
|
@ -103,4 +128,57 @@ abstract class PortletAnnotationMappingUtils {
|
||||||
return true;
|
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<MediaType> requestMediaTypes = MediaType.parseMediaTypes(request.getProperty(key));
|
||||||
|
List<MediaType> valueMediaTypes = MediaType.parseMediaTypes(value);
|
||||||
|
boolean found = false;
|
||||||
|
for (Iterator<MediaType> valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
|
||||||
|
MediaType valueMediaType = valIter.next();
|
||||||
|
for (Iterator<MediaType> 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,7 @@ public class Portlet20AnnotationControllerTests {
|
||||||
RootBeanDefinition bd = new RootBeanDefinition(MyPortlet20DispatchingController.class);
|
RootBeanDefinition bd = new RootBeanDefinition(MyPortlet20DispatchingController.class);
|
||||||
bd.setScope(WebApplicationContext.SCOPE_REQUEST);
|
bd.setScope(WebApplicationContext.SCOPE_REQUEST);
|
||||||
wac.registerBeanDefinition("controller", bd);
|
wac.registerBeanDefinition("controller", bd);
|
||||||
|
wac.registerBeanDefinition("controller2", new RootBeanDefinition(DetailsController.class));
|
||||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(wac);
|
AnnotationConfigUtils.registerAnnotationConfigProcessors(wac);
|
||||||
wac.refresh();
|
wac.refresh();
|
||||||
return wac;
|
return wac;
|
||||||
|
|
@ -543,6 +544,88 @@ public class Portlet20AnnotationControllerTests {
|
||||||
assertEquals("myDefaultResource", resourceResponse.getContentAsString());
|
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
|
* Controllers
|
||||||
|
|
@ -916,6 +999,22 @@ public class Portlet20AnnotationControllerTests {
|
||||||
public void myResource(Writer writer) throws IOException {
|
public void myResource(Writer writer) throws IOException {
|
||||||
writer.write("myResource");
|
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
|
@ResourceMapping
|
||||||
public void myDefaultResource(Writer writer) throws IOException {
|
public void myDefaultResource(Writer writer) throws IOException {
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,13 @@ import java.lang.annotation.Target;
|
||||||
* environments, with the semantics adapting to the concrete environment.
|
* environments, with the semantics adapting to the concrete environment.
|
||||||
*
|
*
|
||||||
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
|
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
|
||||||
* expressed at the class level (if any). HTTP paths / portlet modes need to
|
* expressed at the class level (if any). In the Servlet case, an HTTP path needs to
|
||||||
* uniquely map onto specific handler beans, with any given path / mode only
|
* uniquely map onto one specific handler bean (not spread across multiple handler beans);
|
||||||
* allowed to be mapped onto one specific handler bean (not spread across
|
* the remaining mapping parameters and conditions are effectively assertions only.
|
||||||
* multiple handler beans). It is strongly recommended to co-locate related
|
* In the Portlet case, a portlet mode in combination with specific parameter conditions
|
||||||
* handler methods into the same bean.
|
* 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.
|
||||||
*
|
*
|
||||||
* <p>Handler methods which are annotated with this annotation are allowed
|
* <p>Handler methods which are annotated with this annotation are allowed
|
||||||
* to have very flexible signatures. They may have arguments of the following
|
* 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
|
* When used at the type level, all method-level mappings inherit
|
||||||
* this header restriction (i.e. the type-level restriction
|
* this header restriction (i.e. the type-level restriction
|
||||||
* gets checked before the handler method is even resolved).
|
* gets checked before the handler method is even resolved).
|
||||||
|
* <p>Maps against HttpServletRequest headers in a Servlet environment,
|
||||||
|
* and against PortletRequest properties in a Portlet 2.0 environment.
|
||||||
* @see org.springframework.http.MediaType
|
* @see org.springframework.http.MediaType
|
||||||
*/
|
*/
|
||||||
String[] headers() default {};
|
String[] headers() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue