Integrate suggested support for creating MVC URLs
The key contract is MvcUrls. An instance is automatically created with the Spring MVC namespace and the MVC Java config but can also be easily created in any configuration. Some example tests can be found in DefaultMvcUrlsTests. Issue: SPR-10665, SPR-8826
This commit is contained in:
parent
4fd27b12fc
commit
bafc73f147
|
@ -689,6 +689,7 @@ project("spring-webmvc") {
|
|||
testCompile("commons-io:commons-io:1.3")
|
||||
testCompile("org.hibernate:hibernate-validator:4.3.0.Final")
|
||||
testCompile("org.apache.httpcomponents:httpclient:4.3-beta2")
|
||||
testCompile("joda-time:joda-time:2.2")
|
||||
}
|
||||
|
||||
// pick up DispatcherServlet.properties in src/main
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simply helper to reference a dedicated attribute of an {@link Annotation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationAttribute {
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
private final String attributeName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} to the {@code value} attribute of the
|
||||
* given {@link Annotation} type.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType) {
|
||||
this(annotationType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} for the given {@link Annotation} type and
|
||||
* annotation attribute name.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @param attributeName can be {@literal null}, defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType,
|
||||
String attributeName) {
|
||||
|
||||
Assert.notNull(annotationType);
|
||||
|
||||
this.annotationType = annotationType;
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the annotation type.
|
||||
*
|
||||
* @return the annotationType
|
||||
*/
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return annotationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given
|
||||
* {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object getValueFrom(MethodParameter parameter) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
Annotation annotation = parameter.getParameterAnnotation(annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object findValueOn(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(type, annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
public Object findValueOn(Method method) {
|
||||
|
||||
Assert.notNull(method, "Method must nor be null!");
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
public Object getValueFrom(AnnotatedElement element) {
|
||||
|
||||
Assert.notNull(element, "Annotated element must not be null!");
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(element, annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Annotation} attribute's value from the given {@link Annotation}.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object getValueFrom(Annotation annotation) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
return attributeName == null ? AnnotationUtils.getValue(annotation)
|
||||
: AnnotationUtils.getValue(annotation, attributeName);
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent {@link MethodParameters} to allow to easily find the ones
|
||||
* with a given annotation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MethodParameters {
|
||||
|
||||
private static final ParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private final List<MethodParameter> parameters;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameters} from the given {@link Method}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
*/
|
||||
public MethodParameters(Method method) {
|
||||
this(method, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameters} for the given {@link Method} and
|
||||
* {@link AnnotationAttribute}. If the latter is given, method parameter names will be
|
||||
* looked up from the annotation attribute if present.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param namingAnnotation can be {@literal null}.
|
||||
*/
|
||||
public MethodParameters(Method method, AnnotationAttribute namingAnnotation) {
|
||||
|
||||
Assert.notNull(method);
|
||||
this.parameters = new ArrayList<MethodParameter>();
|
||||
|
||||
for (int i = 0; i < method.getParameterTypes().length; i++) {
|
||||
|
||||
MethodParameter parameter = new AnnotationNamingMethodParameter(method, i,
|
||||
namingAnnotation);
|
||||
parameter.initParameterNameDiscovery(DISCOVERER);
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link MethodParameter}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<MethodParameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodParameter} with the given name or {@literal null} if none
|
||||
* found.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public MethodParameter getParameter(String name) {
|
||||
|
||||
Assert.hasText(name, "Parameter name must not be null!");
|
||||
|
||||
for (MethodParameter parameter : parameters) {
|
||||
if (name.equals(parameter.getParameterName())) {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link MethodParameter}s annotated with the given annotation type.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<MethodParameter> getParametersWith(Class<? extends Annotation> annotation) {
|
||||
|
||||
Assert.notNull(annotation);
|
||||
List<MethodParameter> result = new ArrayList<MethodParameter>();
|
||||
|
||||
for (MethodParameter parameter : getParameters()) {
|
||||
if (parameter.hasParameterAnnotation(annotation)) {
|
||||
result.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@link MethodParameter} extension that will favor the name configured in the
|
||||
* {@link AnnotationAttribute} if set over discovering it.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class AnnotationNamingMethodParameter extends MethodParameter {
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationNamingMethodParameter} for the given
|
||||
* {@link Method}'s parameter with the given index.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameterIndex
|
||||
* @param attribute can be {@literal null}
|
||||
*/
|
||||
public AnnotationNamingMethodParameter(Method method, int parameterIndex,
|
||||
AnnotationAttribute attribute) {
|
||||
|
||||
super(method, parameterIndex);
|
||||
this.attribute = attribute;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.core.MethodParameter#getParameterName()
|
||||
*/
|
||||
@Override
|
||||
public String getParameterName() {
|
||||
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (attribute != null) {
|
||||
Object foundName = attribute.getValueFrom(this);
|
||||
if (foundName != null) {
|
||||
name = foundName.toString();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
name = super.getParameterName();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationAttribute}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationAttributeUnitTests {
|
||||
|
||||
AnnotationAttribute valueAttribute = new AnnotationAttribute(MyAnnotation.class);
|
||||
|
||||
AnnotationAttribute nonValueAttribute = new AnnotationAttribute(MyAnnotation.class,
|
||||
"nonValue");
|
||||
|
||||
@Test
|
||||
public void readsAttributesFromType() {
|
||||
|
||||
assertThat(valueAttribute.findValueOn(Sample.class), is((Object) "foo"));
|
||||
assertThat(nonValueAttribute.findValueOn(Sample.class), is((Object) "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findsAttributesFromSubType() {
|
||||
assertThat(valueAttribute.findValueOn(SampleSub.class), is((Object) "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotGetValueFromSubTyp() {
|
||||
assertThat(valueAttribute.getValueFrom(SampleSub.class), is(nullValue()));
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface MyAnnotation {
|
||||
|
||||
String value() default "";
|
||||
|
||||
String nonValue() default "";
|
||||
}
|
||||
|
||||
@MyAnnotation(value = "foo", nonValue = "bar")
|
||||
static class Sample {
|
||||
|
||||
}
|
||||
|
||||
static class SampleSub extends Sample {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MethodParameters}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MethodParametersUnitTests {
|
||||
|
||||
@Test
|
||||
public void prefersAnnotatedParameterOverDiscovered() throws Exception {
|
||||
|
||||
Method method = Sample.class.getMethod("method", String.class, String.class);
|
||||
MethodParameters parameters = new MethodParameters(method,
|
||||
new AnnotationAttribute(Qualifier.class));
|
||||
|
||||
assertThat(parameters.getParameter("param"), is(notNullValue()));
|
||||
assertThat(parameters.getParameter("foo"), is(notNullValue()));
|
||||
assertThat(parameters.getParameter("another"), is(nullValue()));
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface Qualifier {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
public void method(String param, @Qualifier("foo") String another) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,8 @@ import org.springframework.beans.BeanUtils;
|
|||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -38,10 +40,12 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.UriComponentsContributor;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
|
@ -69,7 +73,10 @@ import org.springframework.web.util.WebUtils;
|
|||
* @since 3.1
|
||||
* @see RequestParamMapMethodArgumentResolver
|
||||
*/
|
||||
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
|
||||
implements UriComponentsContributor {
|
||||
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
private final boolean useDefaultResolution;
|
||||
|
||||
|
@ -83,7 +90,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
* request parameter name is derived from the method parameter name.
|
||||
*/
|
||||
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
|
||||
boolean useDefaultResolution) {
|
||||
boolean useDefaultResolution) {
|
||||
|
||||
super(beanFactory);
|
||||
this.useDefaultResolution = useDefaultResolution;
|
||||
}
|
||||
|
@ -218,6 +226,39 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class.equals(paramType) ||
|
||||
"javax.servlet.http.Part".equals(paramType.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
|
||||
|
||||
if (value == null) {
|
||||
builder.queryParam(name);
|
||||
}
|
||||
else if (value instanceof Collection) {
|
||||
for (Object v : (Collection) value) {
|
||||
v = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), v);
|
||||
builder.queryParam(name, v);
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
|
||||
}
|
||||
}
|
||||
|
||||
protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
|
||||
return (cs != null) ?
|
||||
(String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR) : null;
|
||||
}
|
||||
|
||||
|
||||
private class RequestParamNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private RequestParamNamedValueInfo() {
|
||||
|
@ -228,4 +269,5 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
super(annotation.value(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Strategy for contributing to the building of a {@link UriComponents} by
|
||||
* looking at a method parameter and an argument value and deciding what
|
||||
* part of the target URL should be updated.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface UriComponentsContributor {
|
||||
|
||||
/**
|
||||
* Whether this contributor supports the given method parameter.
|
||||
*/
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Process the given method argument and either update the
|
||||
* {@link UriComponentsBuilder} or add to the map with URI variables to use to
|
||||
* expand the URI after all arguments are processed.
|
||||
*
|
||||
* @param parameter the controller method parameter, never {@literal null}.
|
||||
* @param value the argument value, possibly {@literal null}.
|
||||
* @param builder the builder to update, never {@literal null}.
|
||||
* @param uriVariables a map to add URI variables to, never {@literal null}.
|
||||
* @param conversionService a ConversionService to format values as Strings
|
||||
*/
|
||||
void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder,
|
||||
Map<String, Object> uriVariables, ConversionService conversionService);
|
||||
|
||||
}
|
|
@ -248,7 +248,7 @@ public class UriComponentsBuilder {
|
|||
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// build methods
|
||||
|
||||
|
@ -362,6 +362,43 @@ public class UriComponentsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all components of this URI builder from the given {@link UriComponents}.
|
||||
* @param uriComponents the UriComponents instance
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
|
||||
Assert.notNull(uriComponents, "'uriComponents' must not be null");
|
||||
this.scheme = uriComponents.getScheme();
|
||||
if (uriComponents instanceof OpaqueUriComponents) {
|
||||
this.ssp = uriComponents.getSchemeSpecificPart();
|
||||
resetHierarchicalComponents();
|
||||
}
|
||||
else {
|
||||
if (uriComponents.getUserInfo() != null) {
|
||||
this.userInfo = uriComponents.getUserInfo();
|
||||
}
|
||||
if (uriComponents.getHost() != null) {
|
||||
this.host = uriComponents.getHost();
|
||||
}
|
||||
if (uriComponents.getPort() != -1) {
|
||||
this.port = uriComponents.getPort();
|
||||
}
|
||||
if (StringUtils.hasLength(uriComponents.getPath())) {
|
||||
this.pathBuilder = new CompositePathComponentBuilder(uriComponents.getPath());
|
||||
}
|
||||
if (!uriComponents.getQueryParams().isEmpty()) {
|
||||
this.queryParams.clear();
|
||||
this.queryParams.putAll(uriComponents.getQueryParams());
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
}
|
||||
if (uriComponents.getFragment() != null) {
|
||||
this.fragment = uriComponents.getFragment();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI scheme-specific-part. When invoked, this method overwrites
|
||||
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
|
||||
|
@ -554,36 +591,6 @@ public class UriComponentsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public UriComponentsBuilder with(UriComponentsBuilder builder) {
|
||||
|
||||
UriComponents components = builder.build().normalize();
|
||||
|
||||
if (StringUtils.hasText(components.getScheme())) {
|
||||
scheme(components.getScheme());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(components.getHost())) {
|
||||
host(components.getHost());
|
||||
}
|
||||
|
||||
if (components.getPort() != -1) {
|
||||
port(components.getPort());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(components.getPath())) {
|
||||
path(components.getPath());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(components.getQuery())) {
|
||||
query(components.getQuery());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(components.getFragment())) {
|
||||
fragment(components.getFragment());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private interface PathComponentBuilder {
|
||||
|
||||
|
|
|
@ -207,6 +207,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
|
||||
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
|
||||
|
||||
String mvcUrlsName = "mvcUrls";
|
||||
RootBeanDefinition mvcUrlsDef = new RootBeanDefinition(DefaultMvcUrlsFactoryBean.class);
|
||||
mvcUrlsDef.setSource(source);
|
||||
mvcUrlsDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
|
||||
mvcUrlsDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
|
||||
parserContext.getReaderContext().getRegistry().registerBeanDefinition(mvcUrlsName, mvcUrlsDef);
|
||||
|
||||
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
|
||||
csInterceptorDef.setSource(source);
|
||||
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
|
||||
|
@ -242,6 +249,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(mvcUrlsDef, mvcUrlsName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.UriComponentsContributor;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultMvcUrls;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrls;
|
||||
|
||||
|
||||
/**
|
||||
* A factory bean for creating an instance of {@link MvcUrls} that discovers
|
||||
* {@link UriComponentsContributor}s by obtaining the
|
||||
* {@link HandlerMethodArgumentResolver}s configured in a
|
||||
* {@link RequestMappingHandlerAdapter}.
|
||||
* <p>
|
||||
* This is mainly provided as a convenience in XML configuration. Otherwise call the
|
||||
* constructors of {@link DefaultMvcUrls} directly. Also note the MVC Java config and
|
||||
* XML namespace already create an instance of {@link MvcUrls} and when using either
|
||||
* of them this {@code FactoryBean} is not needed.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class DefaultMvcUrlsFactoryBean implements InitializingBean, FactoryBean<MvcUrls> {
|
||||
|
||||
private RequestMappingHandlerAdapter handlerAdapter;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
private MvcUrls mvcUrls;
|
||||
|
||||
|
||||
/**
|
||||
* Provide a {@link RequestMappingHandlerAdapter} from which to obtain
|
||||
* the list of configured {@link HandlerMethodArgumentResolver}s. This
|
||||
* is provided for ease of configuration in XML.
|
||||
*/
|
||||
public void setHandlerAdapter(RequestMappingHandlerAdapter handlerAdapter) {
|
||||
this.handlerAdapter = handlerAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link ConversionService} instance that {@link MvcUrls} should
|
||||
* use to format Object values being added to a URI.
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.mvcUrls = new DefaultMvcUrls(this.handlerAdapter.getArgumentResolvers(), this.conversionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MvcUrls getObject() throws Exception {
|
||||
return this.mvcUrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MvcUrls.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -78,6 +78,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
|
|||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultMvcUrls;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrls;
|
||||
|
||||
/**
|
||||
* This is the main class providing the configuration behind the MVC Java config.
|
||||
|
@ -563,6 +565,15 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of {@link MvcUrls} for injection into controllers to create
|
||||
* URLs by referencing controllers and controller methods.
|
||||
*/
|
||||
@Bean
|
||||
public MvcUrls mvcUrls() {
|
||||
return new DefaultMvcUrls(requestMappingHandlerAdapter().getArgumentResolvers(), mvcConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link HttpRequestHandlerAdapter} for processing requests
|
||||
* with {@link HttpRequestHandler}s.
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.AnnotationAttribute;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.MethodParameters;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.hypermedia.RecordedInvocationUtils.RecordedMethodInvocation;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Value object to allow accessing {@link RecordedMethodInvocation} parameters with the
|
||||
* configured {@link AnnotationAttribute}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class AnnotatedParametersParameterAccessor {
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotatedParametersParameterAccessor} using the given
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param attribute must not be {@literal null}.
|
||||
*/
|
||||
public AnnotatedParametersParameterAccessor(AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(attribute);
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link BoundMethodParameter}s contained in the given
|
||||
* {@link RecordedMethodInvocation}.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<BoundMethodParameter> getBoundParameters(RecordedMethodInvocation invocation) {
|
||||
|
||||
Assert.notNull(invocation, "RecordedMethodInvocation must not be null!");
|
||||
|
||||
MethodParameters parameters = new MethodParameters(invocation.getMethod());
|
||||
Object[] arguments = invocation.getArguments();
|
||||
List<BoundMethodParameter> result = new ArrayList<BoundMethodParameter>();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParametersWith(attribute.getAnnotationType())) {
|
||||
result.add(new BoundMethodParameter(parameter,
|
||||
arguments[parameter.getParameterIndex()], attribute));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a {@link MethodParameter} alongside the value it has been bound to.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class BoundMethodParameter {
|
||||
|
||||
private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
|
||||
|
||||
private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
private final MethodParameter parameter;
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
|
||||
private final TypeDescriptor parameterTypeDecsriptor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BoundMethodParameter}
|
||||
*
|
||||
* @param parameter
|
||||
* @param value
|
||||
* @param attribute
|
||||
*/
|
||||
public BoundMethodParameter(MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.parameterTypeDecsriptor = TypeDescriptor.nested(parameter, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the {@link UriTemplate} variable to be bound. The name will
|
||||
* be derived from the configured {@link AnnotationAttribute} or the
|
||||
* {@link MethodParameter} name as fallback.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
return parameter.getParameterName();
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation).toString();
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue
|
||||
: parameter.getParameterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value bound to the {@link MethodParameter}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound value converted into a {@link String} based on default
|
||||
* conversion service setup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) CONVERSION_SERVICE.convert(value, parameterTypeDecsriptor,
|
||||
STRING_DESCRIPTOR);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.AnnotationAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link MappingDiscoverer} implementation that inspects mappings from a particular
|
||||
* annotation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class AnnotationMappingDiscoverer {
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type. Will
|
||||
* lookup the {@code value} attribute by default.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation) {
|
||||
this(new AnnotationAttribute(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type and
|
||||
* attribute name.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @param mappingAttributeName if {@literal null}, it defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationMappingDiscoverer(AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(attribute);
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mapping associated with the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return the type-level mapping or {@literal null} in case none is present.
|
||||
*/
|
||||
public String getMapping(Class<?> type) {
|
||||
|
||||
String[] mapping = getMappingFrom(attribute.findValueOn(type));
|
||||
|
||||
if (mapping.length > 1) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Multiple class level mappings defined on class %s!", type.getName()));
|
||||
}
|
||||
|
||||
return mapping.length == 0 ? null : mapping[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mapping associated with the given {@link Method}. This will include the
|
||||
* type-level mapping.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return the method mapping including the type-level one or {@literal null} if
|
||||
* neither of them present.
|
||||
*/
|
||||
public String getMapping(Method method) {
|
||||
|
||||
String[] mapping = getMappingFrom(attribute.findValueOn(method));
|
||||
|
||||
if (mapping.length > 1) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Multiple method level mappings defined on method %s!",
|
||||
method.toString()));
|
||||
}
|
||||
|
||||
String typeMapping = getMapping(method.getDeclaringClass());
|
||||
|
||||
if (mapping == null || mapping.length == 0) {
|
||||
return typeMapping;
|
||||
}
|
||||
|
||||
return typeMapping == null || "/".equals(typeMapping) ? mapping[0] : typeMapping
|
||||
+ mapping[0];
|
||||
}
|
||||
|
||||
private String[] getMappingFrom(Object annotationValue) {
|
||||
|
||||
if (annotationValue instanceof String) {
|
||||
return new String[] { (String) annotationValue };
|
||||
}
|
||||
else if (annotationValue instanceof String[]) {
|
||||
return (String[]) annotationValue;
|
||||
}
|
||||
else if (annotationValue == null) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Unsupported type for the mapping attribute! Support String and String[] but got %s!",
|
||||
annotationValue.getClass()));
|
||||
}
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.AnnotationAttribute;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.MethodParameters;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.hypermedia.AnnotatedParametersParameterAccessor.BoundMethodParameter;
|
||||
import org.springframework.web.servlet.hypermedia.RecordedInvocationUtils.LastInvocationAware;
|
||||
import org.springframework.web.servlet.hypermedia.RecordedInvocationUtils.RecordedMethodInvocation;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
//import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
import static org.springframework.web.servlet.hypermedia.RecordedInvocationUtils.*;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link URI} instances pointing to Spring MVC controllers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MvcUriComponentsBuilder extends UriComponentsBuilder {
|
||||
|
||||
private static final AnnotationMappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(
|
||||
RequestMapping.class);
|
||||
|
||||
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor(
|
||||
new AnnotationAttribute(PathVariable.class));
|
||||
|
||||
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new AnnotatedParametersParameterAccessor(
|
||||
new AnnotationAttribute(RequestParam.class));
|
||||
|
||||
private final List<UriComponentsContributor> contributors;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RequestMappingHandlerAdapter adapter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilderSupport} to grab the
|
||||
* {@link UriComponentsContributor}s registered in the
|
||||
* {@link RequestMappingHandlerAdapter}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
MvcUriComponentsBuilder() {
|
||||
|
||||
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
|
||||
List<UriComponentsContributor> contributors = new ArrayList<UriComponentsContributor>();
|
||||
|
||||
if (adapter != null) {
|
||||
for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers()) {
|
||||
if (resolver instanceof UriComponentsContributor) {
|
||||
contributors.add((UriComponentsContributor) resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.contributors = contributors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MvcUriComponentsBuilder} with a base of the mapping annotated
|
||||
* to the given controller class.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static UriComponentsBuilder from(Class<?> controller) {
|
||||
return from(controller, new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MvcUriComponentsBuilder} with a base of the mapping annotated
|
||||
* to the given controller class. The additional parameters are used to fill up
|
||||
* potentially available path variables in the class scop request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be
|
||||
* {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the
|
||||
* annotation, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static UriComponentsBuilder from(Class<?> controller, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller);
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
UriTemplate template = new UriTemplate(mapping == null ? "/" : mapping);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(template.expand(parameters));
|
||||
return getRootBuilder().with(builder);
|
||||
}
|
||||
|
||||
public static UriComponentsBuilder from(Method method, Object... parameters) {
|
||||
MvcUriComponentsBuilder builder = new MvcUriComponentsBuilder();
|
||||
return from(method, parameters, builder.contributors);
|
||||
}
|
||||
|
||||
static UriComponentsBuilder from(Method method, Object[] parameters,
|
||||
List<UriComponentsContributor> contributors) {
|
||||
|
||||
UriTemplate template = new UriTemplate(DISCOVERER.getMapping(method));
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(template.expand(parameters));
|
||||
|
||||
RecordedMethodInvocation invocation = getInvocation(method, parameters);
|
||||
UriComponentsBuilder appender = applyUriComponentsContributer(invocation,
|
||||
builder, contributors);
|
||||
|
||||
return getRootBuilder().with(appender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MvcUriComponentsBuilder} pointing to a controller method. Hand in
|
||||
* a dummy method invocation result you can create via
|
||||
* {@link #methodOn(Class, Object...)} or
|
||||
* {@link RecordedInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* <pre>
|
||||
* @RequestMapping("/customers")
|
||||
* class CustomerController {
|
||||
*
|
||||
* @RequestMapping("/{id}/addresses")
|
||||
* HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
|
||||
* }
|
||||
*
|
||||
* URI uri = linkTo(methodOn(CustomerController.class).showAddresses(2L)).toURI();
|
||||
* </pre>
|
||||
*
|
||||
* The resulting {@link URI} instance will point to {@code /customers/2/addresses}.
|
||||
* For more details on the method invocation constraints, see
|
||||
* {@link RecordedInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object)
|
||||
*/
|
||||
public static UriComponentsBuilder from(Object invocationValue) {
|
||||
|
||||
MvcUriComponentsBuilder builder = new MvcUriComponentsBuilder();
|
||||
return from(invocationValue, builder.contributors);
|
||||
}
|
||||
|
||||
static UriComponentsBuilder from(Object invocationValue,
|
||||
List<? extends UriComponentsContributor> contributors) {
|
||||
|
||||
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
|
||||
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
|
||||
|
||||
RecordedMethodInvocation invocation = invocations.getLastInvocation();
|
||||
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
|
||||
Method method = invocation.getMethod();
|
||||
|
||||
String mapping = DISCOVERER.getMapping(method);
|
||||
UriComponentsBuilder builder = getRootBuilder().path(mapping);
|
||||
|
||||
UriTemplate template = new UriTemplate(mapping);
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
while (classMappingParameters.hasNext()) {
|
||||
values.put(names.next(), classMappingParameters.next());
|
||||
}
|
||||
|
||||
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
|
||||
values.put(parameter.getVariableName(), parameter.asString());
|
||||
}
|
||||
|
||||
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
|
||||
|
||||
Object value = parameter.getValue();
|
||||
String key = parameter.getVariableName();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
for (Object element : (Collection<?>) value) {
|
||||
builder.queryParam(key, element);
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.queryParam(key, parameter.asString());
|
||||
}
|
||||
}
|
||||
|
||||
UriComponents components = applyUriComponentsContributer(invocation, builder,
|
||||
contributors).buildAndExpand(values);
|
||||
return UriComponentsBuilder.fromUri(components.toUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link RecordedInvocationUtils#methodOn(Class, Object...)} to be
|
||||
* available in case you work with static imports of {@link MvcUriComponentsBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level
|
||||
* mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
return RecordedInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping
|
||||
* with the host tweaked in case the request contains an {@code X-Forwarded-Host}
|
||||
* header.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static UriComponentsBuilder getRootBuilder() {
|
||||
|
||||
HttpServletRequest request = getCurrentRequest();
|
||||
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
|
||||
|
||||
String header = request.getHeader("X-Forwarded-Host");
|
||||
|
||||
if (!StringUtils.hasText(header)) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
String[] hosts = StringUtils.commaDelimitedListToStringArray(header);
|
||||
String hostToUse = hosts[0];
|
||||
|
||||
if (hostToUse.contains(":")) {
|
||||
|
||||
String[] hostAndPort = StringUtils.split(hostToUse, ":");
|
||||
|
||||
builder.host(hostAndPort[0]);
|
||||
builder.port(Integer.parseInt(hostAndPort[1]));
|
||||
|
||||
}
|
||||
else {
|
||||
builder.host(hostToUse);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured {@link UriComponentsContributor}s to the given
|
||||
* {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @param builder will never be {@literal null}.
|
||||
* @param invocation will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static UriComponentsBuilder applyUriComponentsContributer(
|
||||
RecordedMethodInvocation invocation, UriComponentsBuilder builder,
|
||||
Collection<? extends UriComponentsContributor> contributors) {
|
||||
|
||||
if (contributors.isEmpty()) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
MethodParameters parameters = new MethodParameters(invocation.getMethod());
|
||||
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParameters()) {
|
||||
Object parameterValue = parameterValues.next();
|
||||
for (UriComponentsContributor contributor : contributors) {
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
contributor.enhance(builder, parameter, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110
|
||||
* gets fixed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static HttpServletRequest getCurrentRequest() {
|
||||
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
Assert.state(requestAttributes != null,
|
||||
"Could not find current request via RequestContextHolder");
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
|
||||
HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
|
||||
return servletRequest;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author olivergierke
|
||||
*/
|
||||
public class MvcUriComponentsBuilderFactory implements MvcUris {
|
||||
|
||||
private final List<? extends UriComponentsContributor> contributors;
|
||||
|
||||
/**
|
||||
* @param contributors
|
||||
*/
|
||||
public MvcUriComponentsBuilderFactory(
|
||||
List<? extends UriComponentsContributor> contributors) {
|
||||
this.contributors = contributors;
|
||||
}
|
||||
|
||||
public UriComponentsBuilder from(Class<?> controller) {
|
||||
return from(controller, new Object[0]);
|
||||
}
|
||||
|
||||
public UriComponentsBuilder from(Class<?> controller, Object... parameters) {
|
||||
return MvcUriComponentsBuilder.from(controller, parameters);
|
||||
}
|
||||
|
||||
public UriComponentsBuilder from(Object invocationValue) {
|
||||
return MvcUriComponentsBuilder.from(invocationValue, contributors);
|
||||
}
|
||||
|
||||
public UriComponentsBuilder from(Method method, Object... parameters) {
|
||||
return MvcUriComponentsBuilder.from(method, parameters, contributors);
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author olivergierke
|
||||
*/
|
||||
public interface MvcUris {
|
||||
|
||||
UriComponentsBuilder from(Class<?> controller);
|
||||
|
||||
UriComponentsBuilder from(Class<?> controller, Object... parameters);
|
||||
|
||||
UriComponentsBuilder from(Object invocationValue);
|
||||
|
||||
UriComponentsBuilder from(Method method, Object... parameters);
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utility methods to capture dummy method invocations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RecordedInvocationUtils {
|
||||
|
||||
private static ObjenesisStd OBJENESIS = new ObjenesisStd(true);
|
||||
|
||||
public interface LastInvocationAware {
|
||||
|
||||
Iterator<Object> getObjectParameters();
|
||||
|
||||
RecordedMethodInvocation getLastInvocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method interceptor that records the last method invocation and creates a proxy for
|
||||
* the return value that exposes the method invocation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class InvocationRecordingMethodInterceptor implements
|
||||
MethodInterceptor, LastInvocationAware,
|
||||
org.springframework.cglib.proxy.MethodInterceptor {
|
||||
|
||||
private static final Method GET_INVOCATIONS;
|
||||
|
||||
private static final Method GET_OBJECT_PARAMETERS;
|
||||
|
||||
private final Object[] objectParameters;
|
||||
|
||||
private RecordedMethodInvocation invocation;
|
||||
|
||||
static {
|
||||
GET_INVOCATIONS = ReflectionUtils.findMethod(LastInvocationAware.class,
|
||||
"getLastInvocation");
|
||||
GET_OBJECT_PARAMETERS = ReflectionUtils.findMethod(LastInvocationAware.class,
|
||||
"getObjectParameters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link InvocationRecordingMethodInterceptor} carrying the given
|
||||
* parameters forward that might be needed to populate the class level mapping.
|
||||
*
|
||||
* @param parameters
|
||||
*/
|
||||
public InvocationRecordingMethodInterceptor(Object... parameters) {
|
||||
this.objectParameters = parameters.clone();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
|
||||
* java.lang.reflect.Method, java.lang.Object[],
|
||||
* org.springframework.cglib.proxy.MethodProxy)
|
||||
*/
|
||||
public Object intercept(Object obj, Method method, Object[] args,
|
||||
MethodProxy proxy) {
|
||||
|
||||
if (GET_INVOCATIONS.equals(method)) {
|
||||
return getLastInvocation();
|
||||
}
|
||||
else if (GET_OBJECT_PARAMETERS.equals(method)) {
|
||||
return getObjectParameters();
|
||||
}
|
||||
else if (ReflectionUtils.isObjectMethod(method)) {
|
||||
return ReflectionUtils.invokeMethod(method, obj, args);
|
||||
}
|
||||
|
||||
this.invocation = new SimpleRecordedMethodInvocation(method, args);
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
return returnType.cast(getProxyWithInterceptor(returnType, this));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept
|
||||
* .MethodInvocation)
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation)
|
||||
throws Throwable {
|
||||
return intercept(invocation.getThis(), invocation.getMethod(),
|
||||
invocation.getArguments(), null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware#
|
||||
* getLastInvocation()
|
||||
*/
|
||||
@Override
|
||||
public RecordedMethodInvocation getLastInvocation() {
|
||||
return invocation;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware#
|
||||
* getObjectParameters()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Object> getObjectParameters() {
|
||||
return Arrays.asList(objectParameters).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a proxy of the given type, backed by an {@link EmptyTargetSource} to simply
|
||||
* drop method invocations but equips it with an
|
||||
* {@link InvocationRecordingMethodInterceptor}. The interceptor records the last
|
||||
* invocation and returns a proxy of the return type that also implements
|
||||
* {@link LastInvocationAware} so that the last method invocation can be inspected.
|
||||
* Parameters passed to the subsequent method invocation are generally neglected
|
||||
* except the ones that might be mapped into the URI translation eventually, e.g.
|
||||
* {@linke PathVariable} in the case of Spring MVC.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level
|
||||
* mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> type, Object... parameters) {
|
||||
|
||||
Assert.notNull(type, "Given type must not be null!");
|
||||
|
||||
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(
|
||||
parameters);
|
||||
return getProxyWithInterceptor(type, interceptor);
|
||||
}
|
||||
|
||||
static RecordedMethodInvocation getInvocation(Method method, Object[] parameters) {
|
||||
return new SimpleRecordedMethodInvocation(method, parameters);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getProxyWithInterceptor(Class<?> type,
|
||||
InvocationRecordingMethodInterceptor interceptor) {
|
||||
|
||||
if (type.isInterface()) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
|
||||
factory.addInterface(type);
|
||||
factory.addInterface(LastInvocationAware.class);
|
||||
factory.addAdvice(interceptor);
|
||||
|
||||
return (T) factory.getProxy();
|
||||
}
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(type);
|
||||
enhancer.setInterfaces(new Class<?>[] { LastInvocationAware.class });
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
|
||||
Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass());
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
return (T) factory;
|
||||
}
|
||||
|
||||
public interface RecordedMethodInvocation {
|
||||
|
||||
Object[] getArguments();
|
||||
|
||||
Method getMethod();
|
||||
}
|
||||
|
||||
static class SimpleRecordedMethodInvocation implements RecordedMethodInvocation {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Object[] arguments;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleRecordedMethodInvocation} for the given
|
||||
* {@link Method} and arguments.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param arguments must not be {@literal null}.
|
||||
*/
|
||||
private SimpleRecordedMethodInvocation(Method method, Object[] arguments) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(arguments, "Arguments must not be null!");
|
||||
|
||||
this.arguments = arguments;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* SPI callback to enhance a {@link UriComponentsBuilder} when referring to a method
|
||||
* through a dummy method invocation. Will usually be implemented in implementations of
|
||||
* {@link HandlerMethodArgumentResolver} as they represent exactly the same functionality
|
||||
* inverted.
|
||||
*
|
||||
* @see MvcUriComponentsBuilderFactory#from(Object)
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface UriComponentsContributor {
|
||||
|
||||
/**
|
||||
* Returns whether the {@link UriComponentsBuilder} supports the given
|
||||
* {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Enhance the given {@link UriComponentsBuilder} with the given value.
|
||||
*
|
||||
* @param builder will never be {@literal null}.
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
*/
|
||||
void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value);
|
||||
}
|
|
@ -21,6 +21,8 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
|
@ -32,8 +34,10 @@ import org.springframework.web.context.request.RequestAttributes;
|
|||
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.method.support.UriComponentsContributor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Resolves method arguments annotated with an @{@link PathVariable}.
|
||||
|
@ -59,7 +63,11 @@ import org.springframework.web.servlet.View;
|
|||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
|
||||
implements UriComponentsContributor {
|
||||
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
|
||||
public PathVariableMethodArgumentResolver() {
|
||||
super(null);
|
||||
|
@ -114,6 +122,25 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
pathVars.put(name, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
PathVariable annot = parameter.getParameterAnnotation(PathVariable.class);
|
||||
String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
|
||||
|
||||
if (conversionService != null) {
|
||||
value = conversionService.convert(value, new TypeDescriptor(parameter), STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
|
||||
uriVariables.put(name, value);
|
||||
}
|
||||
|
||||
|
||||
private static class PathVariableNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private PathVariableNamedValueInfo(PathVariable annotation) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Map.Entry;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
@ -219,7 +220,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
* not initialized yet via {@link #afterPropertiesSet()}.
|
||||
*/
|
||||
public List<HandlerMethodArgumentResolver> getArgumentResolvers() {
|
||||
return this.argumentResolvers.getResolvers();
|
||||
return (this.argumentResolvers != null) ? this.argumentResolvers.getResolvers() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,7 +241,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
* {@code null} if not initialized yet via {@link #afterPropertiesSet()}.
|
||||
*/
|
||||
public List<HandlerMethodArgumentResolver> getInitBinderArgumentResolvers() {
|
||||
return this.initBinderArgumentResolvers.getResolvers();
|
||||
return (this.initBinderArgumentResolvers != null) ? this.initBinderArgumentResolvers.getResolvers() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.UriComponentsContributor;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrlUtils.ControllerMethodValues;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* A default {@link MvcUrls} implementation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Rossen Stoyanchev
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class DefaultMvcUrls implements MvcUrls {
|
||||
|
||||
private static final ParameterNameDiscoverer parameterNameDiscoverer =
|
||||
new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
|
||||
private final List<UriComponentsContributor> contributors = new ArrayList<UriComponentsContributor>();
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance providing a collection of {@link UriComponentsContributor}s or
|
||||
* {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented
|
||||
* by the same class, the most convenient option is to obtain the configured
|
||||
* {@code HandlerMethodArgumentResolvers} in the {@code RequestMappingHandlerAdapter}
|
||||
* and provide that to this contstructor.
|
||||
*
|
||||
* @param uriComponentsContributors a collection of {@link UriComponentsContributor}
|
||||
* or {@link HandlerMethodArgumentResolver}s.
|
||||
*/
|
||||
public DefaultMvcUrls(Collection<?> uriComponentsContributors) {
|
||||
this(uriComponentsContributors, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance providing a collection of {@link UriComponentsContributor}s or
|
||||
* {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented
|
||||
* by the same class, the most convenient option is to obtain the configured
|
||||
* {@code HandlerMethodArgumentResolvers} in the {@code RequestMappingHandlerAdapter}
|
||||
* and provide that to this contstructor.
|
||||
* <p>
|
||||
* If the {@link ConversionService} argument is {@code null},
|
||||
* {@link DefaultFormattingConversionService} will be used by default.
|
||||
*
|
||||
* @param uriComponentsContributors a collection of {@link UriComponentsContributor}
|
||||
* or {@link HandlerMethodArgumentResolver}s.
|
||||
* @param conversionService a ConversionService to use when method argument values
|
||||
* need to be formatted as Strings before being added to the URI
|
||||
*/
|
||||
public DefaultMvcUrls(Collection<?> uriComponentsContributors, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(uriComponentsContributors, "'uriComponentsContributors' must not be null");
|
||||
|
||||
for (Object contributor : uriComponentsContributors) {
|
||||
if (contributor instanceof UriComponentsContributor) {
|
||||
this.contributors.add((UriComponentsContributor) contributor);
|
||||
}
|
||||
}
|
||||
|
||||
this.conversionService = (conversionService != null) ?
|
||||
conversionService : new DefaultFormattingConversionService();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UriComponentsBuilder linkToController(Class<?> controllerClass) {
|
||||
String mapping = MvcUrlUtils.getTypeLevelMapping(controllerClass);
|
||||
return ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriComponents linkToMethod(Method method, Object... argumentValues) {
|
||||
String mapping = MvcUrlUtils.getMethodMapping(method);
|
||||
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping);
|
||||
Map<String, Object> uriVars = new HashMap<String, Object>();
|
||||
return applyContributers(builder, method, argumentValues, uriVars);
|
||||
}
|
||||
|
||||
private UriComponents applyContributers(UriComponentsBuilder builder, Method method,
|
||||
Object[] argumentValues, Map<String, Object> uriVars) {
|
||||
|
||||
if (this.contributors.isEmpty()) {
|
||||
return builder.buildAndExpand(uriVars);
|
||||
}
|
||||
|
||||
int paramCount = method.getParameters().length;
|
||||
int argCount = argumentValues.length;
|
||||
|
||||
Assert.isTrue(paramCount == argCount, "Number of method parameters " + paramCount +
|
||||
" does not match number of argument values " + argCount);
|
||||
|
||||
for (int i=0; i < paramCount; i++) {
|
||||
MethodParameter param = new MethodParameter(method, i);
|
||||
param.initParameterNameDiscovery(parameterNameDiscoverer);
|
||||
for (UriComponentsContributor c : this.contributors) {
|
||||
if (c.supportsParameter(param)) {
|
||||
c.contributeMethodArgument(param, argumentValues[i], builder, uriVars, this.conversionService);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.buildAndExpand(uriVars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriComponents linkToMethodOn(Object mockController) {
|
||||
|
||||
Assert.isInstanceOf(ControllerMethodValues.class, mockController);
|
||||
ControllerMethodValues controllerMethodValues = (ControllerMethodValues) mockController;
|
||||
|
||||
Method method = controllerMethodValues.getControllerMethod();
|
||||
Object[] argumentValues = controllerMethodValues.getArgumentValues();
|
||||
|
||||
Map<String, Object> uriVars = new HashMap<String, Object>();
|
||||
addTypeLevelUriVaris(controllerMethodValues, uriVars);
|
||||
|
||||
String mapping = MvcUrlUtils.getMethodMapping(method);
|
||||
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping);
|
||||
|
||||
return applyContributers(builder, method, argumentValues, uriVars);
|
||||
}
|
||||
|
||||
private void addTypeLevelUriVaris(ControllerMethodValues info, Map<String, Object> uriVariables) {
|
||||
|
||||
Object[] values = info.getTypeLevelUriVariables();
|
||||
if (!ObjectUtils.isEmpty(values)) {
|
||||
|
||||
String mapping = MvcUrlUtils.getTypeLevelMapping(info.getControllerMethod().getDeclaringClass());
|
||||
|
||||
List<String> names = new UriTemplate(mapping).getVariableNames();
|
||||
Assert.isTrue(names.size() == values.length, "The provided type-level URI template variables " +
|
||||
Arrays.toString(values) + " do not match the template " + mapping);
|
||||
|
||||
for (int i=0; i < names.size(); i++) {
|
||||
uriVariables.put(names.get(i), values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
/**
|
||||
* Utility methods to support the creation URLs to Spring MVC controllers and controller
|
||||
* methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Rossen Stoyanchev
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MvcUrlUtils {
|
||||
|
||||
private static Log logger = LogFactory.getLog(MvcUrlUtils.class);
|
||||
|
||||
private final static ObjenesisStd OBJENESIS = new ObjenesisStd(true);
|
||||
|
||||
|
||||
/**
|
||||
* Extract the type-level URL mapping or return an empty String. If multiple mappings
|
||||
* are found, the first one is used.
|
||||
*/
|
||||
public static String getTypeLevelMapping(Class<?> controllerType) {
|
||||
Assert.notNull(controllerType, "'controllerType' must not be null");
|
||||
RequestMapping annot = AnnotationUtils.findAnnotation(controllerType, RequestMapping.class);
|
||||
if ((annot == null) || ObjectUtils.isEmpty(annot.value())) {
|
||||
return "/";
|
||||
}
|
||||
if (annot.value().length > 1) {
|
||||
logger.warn("Multiple class level mappings on " + controllerType.getName() + ", using the first one");
|
||||
}
|
||||
return annot.value()[0];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the mapping from the given controller method, including both type and
|
||||
* method-level mappings. If multiple mappings are found, the first one is used.
|
||||
*/
|
||||
public static String getMethodMapping(Method method) {
|
||||
RequestMapping methodAnnot = AnnotationUtils.findAnnotation(method, RequestMapping.class);
|
||||
Assert.notNull(methodAnnot, "No mappings on " + method.toGenericString());
|
||||
PatternsRequestCondition condition = new PatternsRequestCondition(methodAnnot.value());
|
||||
|
||||
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequestMapping.class);
|
||||
if (typeAnnot != null) {
|
||||
condition = new PatternsRequestCondition(typeAnnot.value()).combine(condition);
|
||||
}
|
||||
|
||||
Set<String> patterns = condition.getPatterns();
|
||||
if (patterns.size() > 1) {
|
||||
logger.warn("Multiple mappings on " + method.toGenericString() + ", using the first one");
|
||||
}
|
||||
|
||||
return (patterns.size() == 0) ? "/" : patterns.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "mock" controller instance. When a controller method is invoked, the
|
||||
* invoked method and argument values are remembered, and a "mock" value is returned
|
||||
* so it can be used to help prepare a {@link UriComponents} through
|
||||
* {@link MvcUrls#linkToMethodOn(Object)}.
|
||||
*
|
||||
* @param controllerType the type of controller to mock, must not be {@literal null}.
|
||||
* @param typeLevelUriVariables URI variables to expand into the type-level mapping
|
||||
* @return the created controller instance
|
||||
*/
|
||||
public static <T> T controller(Class<T> controllerType, Object... typeLevelUriVariables) {
|
||||
Assert.notNull(controllerType, "'type' must not be null");
|
||||
return initProxy(controllerType, new ControllerMethodInvocationInterceptor(typeLevelUriVariables));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T initProxy(Class<?> type, ControllerMethodInvocationInterceptor interceptor) {
|
||||
|
||||
if (type.isInterface()) {
|
||||
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
|
||||
factory.addInterface(type);
|
||||
factory.addInterface(ControllerMethodValues.class);
|
||||
factory.addAdvice(interceptor);
|
||||
return (T) factory.getProxy();
|
||||
}
|
||||
else {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(type);
|
||||
enhancer.setInterfaces(new Class<?>[] { ControllerMethodValues.class });
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
|
||||
Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass());
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
return (T) factory;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ControllerMethodInvocationInterceptor
|
||||
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
|
||||
|
||||
private static final Method getTypeLevelUriVariables =
|
||||
ReflectionUtils.findMethod(ControllerMethodValues.class, "getTypeLevelUriVariables");
|
||||
|
||||
private static final Method getControllerMethod =
|
||||
ReflectionUtils.findMethod(ControllerMethodValues.class, "getControllerMethod");
|
||||
|
||||
private static final Method getArgumentValues =
|
||||
ReflectionUtils.findMethod(ControllerMethodValues.class, "getArgumentValues");
|
||||
|
||||
|
||||
private final Object[] typeLevelUriVariables;
|
||||
|
||||
private Method controllerMethod;
|
||||
|
||||
private Object[] argumentValues;
|
||||
|
||||
|
||||
public ControllerMethodInvocationInterceptor(Object... typeLevelUriVariables) {
|
||||
this.typeLevelUriVariables = typeLevelUriVariables.clone();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
|
||||
|
||||
if (getTypeLevelUriVariables.equals(method)) {
|
||||
return this.typeLevelUriVariables;
|
||||
}
|
||||
else if (getControllerMethod.equals(method)) {
|
||||
return this.controllerMethod;
|
||||
}
|
||||
else if (getArgumentValues.equals(method)) {
|
||||
return this.argumentValues;
|
||||
}
|
||||
else if (ReflectionUtils.isObjectMethod(method)) {
|
||||
return ReflectionUtils.invokeMethod(method, obj, args);
|
||||
}
|
||||
else {
|
||||
this.controllerMethod = method;
|
||||
this.argumentValues = args;
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
return void.class.equals(returnType) ? null : returnType.cast(initProxy(returnType, this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
|
||||
return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides information about a controller method that can be used to prepare a URL
|
||||
* including type-level URI template variables, a method reference, and argument
|
||||
* values collected through the invocation of a "mock" controller.
|
||||
* <p>
|
||||
* Instances of this interface are returned from
|
||||
* {@link MvcUrlUtils#controller(Class, Object...) controller(Class, Object...)} and
|
||||
* are needed for {@link MvcUrls#linkToMethodOn(ControllerMethodValues)}.
|
||||
*/
|
||||
public interface ControllerMethodValues {
|
||||
|
||||
Object[] getTypeLevelUriVariables();
|
||||
|
||||
Method getControllerMethod();
|
||||
|
||||
Object[] getArgumentValues();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.method.support.UriComponentsContributor;
|
||||
import org.springframework.web.servlet.config.DefaultMvcUrlsFactoryBean;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A contract for creating URLs by referencing Spring MVC controllers and methods.
|
||||
* <p>
|
||||
* The MVC Java config and the MVC namespace automatically create an instance of this
|
||||
* contract for use in controllers and anywhere else during the processing of a request.
|
||||
* The best way for access it is to have it autowired, or otherwise injected either by
|
||||
* type or also qualified by name ("mvcUrls") if necessary.
|
||||
* <p>
|
||||
* If not using either option, with explicit configuration it's easy to create an instance
|
||||
* of {@link DefaultMvcUrls} in Java config or in XML configuration, use
|
||||
* {@link DefaultMvcUrlsFactoryBean}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Rossen Stoyanchev
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface MvcUrls {
|
||||
|
||||
/**
|
||||
* Creates a new {@link UriComponentsBuilder} by pointing to a controller class. The
|
||||
* resulting builder contains all the current request information up to and including
|
||||
* the Servlet mapping as well as the portion of the path matching to the controller
|
||||
* level request mapping. If the controller contains multiple mappings, the
|
||||
* {@link DefaultMvcUrls} will use the first one.
|
||||
*
|
||||
* @param controllerType the controller type to create a URL to
|
||||
*
|
||||
* @return a builder that can be used to further build the {@link UriComponents}.
|
||||
*/
|
||||
UriComponentsBuilder linkToController(Class<?> controllerType);
|
||||
|
||||
/**
|
||||
* Create a {@link UriComponents} by pointing to a controller method along with method
|
||||
* argument values.
|
||||
* <p>
|
||||
* Type and method-level mappings of the controller method are extracted and the
|
||||
* resulting {@link UriComponents} is further enriched with method argument values from
|
||||
* {@link PathVariable} and {@link RequestParam} parameters. Any other arguments not
|
||||
* relevant to the building of the URL can be provided as {@literal null} and will be
|
||||
* ignored. Support for additional custom arguments can be added through a
|
||||
* {@link UriComponentsContributor}.
|
||||
*
|
||||
* FIXME Type-level URI template variables?
|
||||
*
|
||||
* @param method the target controller method
|
||||
* @param argumentValues argument values matching to method parameters
|
||||
*
|
||||
* @return UriComponents instance, never {@literal null}
|
||||
*/
|
||||
UriComponents linkToMethod(Method method, Object... argumentValues);
|
||||
|
||||
/**
|
||||
* Create a {@link UriComponents} by invoking a method on a "mock" controller similar
|
||||
* to how test frameworks provide mock objects and record method invocations. The
|
||||
* static method {@link MvcUrlUtils#controller(Class, Object...)} can be used to
|
||||
* create a "mock" controller:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @RequestMapping("/people/{id}/addresses")
|
||||
* class AddressController {
|
||||
*
|
||||
* @RequestMapping("/{country}")
|
||||
* public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) { … }
|
||||
*
|
||||
* @RequestMapping(value="/", method=RequestMethod.POST)
|
||||
* public void addAddress(Address address) { … }
|
||||
* }
|
||||
*
|
||||
* // short-hand style with static import of MvcUrlUtils.controller
|
||||
*
|
||||
* mvcUrls.linkToMethodOn(controller(CustomerController.class, 1).showAddresses("US"));
|
||||
*
|
||||
* // longer style, required for void controller methods
|
||||
*
|
||||
* CustomerController controller = MvcUrlUtils.controller(CustomController.class, 1);
|
||||
* controller.addAddress(null);
|
||||
*
|
||||
* mvcUrls.linkToMethodOn(controller);
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* The above mechanism supports {@link PathVariable} and {@link RequestParam} method
|
||||
* arguments. Any other arguments can be provided as {@literal null} and will be
|
||||
* ignored. Additional custom arguments can be added through an implementation of
|
||||
* {@link UriComponentsContributor}.
|
||||
*
|
||||
* @param mockController created via {@link MvcUrlUtils#controller(Class, Object...)}
|
||||
*
|
||||
* @return UriComponents instance, never {@literal null}
|
||||
*/
|
||||
UriComponents linkToMethodOn(Object mockController);
|
||||
|
||||
}
|
|
@ -100,16 +100,18 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
|
|||
int port = request.getServerPort();
|
||||
String host = request.getServerName();
|
||||
|
||||
String xForwardedHostHeader = request.getHeader("X-Forwarded-Host");
|
||||
String header = request.getHeader("X-Forwarded-Host");
|
||||
|
||||
if (StringUtils.hasText(xForwardedHostHeader)) {
|
||||
if (StringUtils.countOccurrencesOf(xForwardedHostHeader, ":") == 1) {
|
||||
String[] hostAndPort = StringUtils.split(xForwardedHostHeader, ":");
|
||||
if (StringUtils.hasText(header)) {
|
||||
String[] hosts = StringUtils.commaDelimitedListToStringArray(header);
|
||||
String hostToUse = hosts[0];
|
||||
if (hostToUse.contains(":")) {
|
||||
String[] hostAndPort = StringUtils.split(hostToUse, ":");
|
||||
host = hostAndPort[0];
|
||||
port = Integer.parseInt(hostAndPort[1]);
|
||||
}
|
||||
else {
|
||||
host = xForwardedHostHeader;
|
||||
host = hostToUse;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ import org.springframework.web.accept.ContentNegotiationManager;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
|
@ -76,11 +78,14 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
|
|||
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrls;
|
||||
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
|
||||
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
|
||||
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*;
|
||||
|
||||
/**
|
||||
* @author Keith Donald
|
||||
|
@ -108,7 +113,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testDefaultConfig() throws Exception {
|
||||
loadBeanDefinitions("mvc-config.xml", 12);
|
||||
loadBeanDefinitions("mvc-config.xml", 13);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -147,11 +152,26 @@ public class MvcNamespaceTests {
|
|||
|
||||
adapter.handle(request, response, handlerMethod);
|
||||
assertTrue(handler.recordedValidationError);
|
||||
|
||||
// MvcUrls
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
|
||||
try {
|
||||
Date now = new Date();
|
||||
TestController testController = controller(TestController.class);
|
||||
testController.testBind(now, null, null);
|
||||
MvcUrls mvcUrls = this.appContext.getBean(MvcUrls.class);
|
||||
UriComponents uriComponents = mvcUrls.linkToMethodOn(testController);
|
||||
|
||||
assertEquals("http://localhost/?date=2013-10-21", uriComponents.toUriString());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=TypeMismatchException.class)
|
||||
public void testCustomConversionService() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 12);
|
||||
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 13);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -177,7 +197,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testCustomValidator() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-custom-validator.xml", 12);
|
||||
loadBeanDefinitions("mvc-config-custom-validator.xml", 13);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -199,7 +219,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testInterceptors() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 17);
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 18);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -328,7 +348,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testBeanDecoration() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-bean-decoration.xml", 14);
|
||||
loadBeanDefinitions("mvc-config-bean-decoration.xml", 15);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -349,7 +369,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testViewControllers() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 15);
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 16);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -409,7 +429,7 @@ public class MvcNamespaceTests {
|
|||
/** WebSphere gives trailing servlet path slashes by default!! */
|
||||
@Test
|
||||
public void testViewControllersOnWebSphere() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 15);
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 16);
|
||||
|
||||
SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
|
||||
SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
|
||||
|
@ -462,7 +482,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testContentNegotiationManager() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 12);
|
||||
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 13);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
|
||||
|
@ -474,7 +494,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testAsyncSupportOptions() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-async-support.xml", 13);
|
||||
loadBeanDefinitions("mvc-config-async-support.xml", 14);
|
||||
|
||||
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
|
||||
assertNotNull(adapter);
|
||||
|
|
|
@ -16,28 +16,33 @@
|
|||
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
|
||||
|
@ -49,6 +54,11 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
|
|||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrls;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*;
|
||||
|
||||
/**
|
||||
* A test fixture with an {@link WebMvcConfigurationSupport} instance.
|
||||
|
@ -57,27 +67,26 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
|||
*/
|
||||
public class WebMvcConfigurationSupportTests {
|
||||
|
||||
private WebMvcConfigurationSupport mvcConfiguration;
|
||||
|
||||
private StaticWebApplicationContext wac;
|
||||
private WebApplicationContext wac;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.wac = new StaticWebApplicationContext();
|
||||
this.mvcConfiguration = new WebMvcConfigurationSupport();
|
||||
this.mvcConfiguration.setApplicationContext(wac);
|
||||
|
||||
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
|
||||
cxt.setServletContext(new MockServletContext());
|
||||
cxt.register(TestConfig.class);
|
||||
cxt.refresh();
|
||||
|
||||
this.wac = cxt;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMappingHandlerMapping() throws Exception {
|
||||
this.wac.registerSingleton("controller", TestController.class);
|
||||
|
||||
RequestMappingHandlerMapping handlerMapping = mvcConfiguration.requestMappingHandlerMapping();
|
||||
RequestMappingHandlerMapping handlerMapping = this.wac.getBean(RequestMappingHandlerMapping.class);
|
||||
assertEquals(0, handlerMapping.getOrder());
|
||||
|
||||
handlerMapping.setApplicationContext(this.wac);
|
||||
handlerMapping.afterPropertiesSet();
|
||||
HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
|
||||
assertNotNull(chain.getInterceptors());
|
||||
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass());
|
||||
|
@ -85,7 +94,10 @@ public class WebMvcConfigurationSupportTests {
|
|||
|
||||
@Test
|
||||
public void emptyViewControllerHandlerMapping() {
|
||||
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) mvcConfiguration.viewControllerHandlerMapping();
|
||||
|
||||
AbstractHandlerMapping handlerMapping = this.wac.getBean(
|
||||
"viewControllerHandlerMapping", AbstractHandlerMapping.class);
|
||||
|
||||
assertNotNull(handlerMapping);
|
||||
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
|
||||
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
|
||||
|
@ -93,16 +105,13 @@ public class WebMvcConfigurationSupportTests {
|
|||
|
||||
@Test
|
||||
public void beanNameHandlerMapping() throws Exception {
|
||||
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
|
||||
cxt.registerSingleton("/controller", TestController.class);
|
||||
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/controller");
|
||||
|
||||
BeanNameUrlHandlerMapping handlerMapping = mvcConfiguration.beanNameHandlerMapping();
|
||||
BeanNameUrlHandlerMapping handlerMapping = this.wac.getBean(BeanNameUrlHandlerMapping.class);
|
||||
assertEquals(2, handlerMapping.getOrder());
|
||||
|
||||
handlerMapping.setApplicationContext(cxt);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/testController");
|
||||
HandlerExecutionChain chain = handlerMapping.getHandler(request);
|
||||
|
||||
assertNotNull(chain.getInterceptors());
|
||||
assertEquals(2, chain.getInterceptors().length);
|
||||
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
|
||||
|
@ -110,8 +119,10 @@ public class WebMvcConfigurationSupportTests {
|
|||
|
||||
@Test
|
||||
public void emptyResourceHandlerMapping() {
|
||||
mvcConfiguration.setApplicationContext(new StaticWebApplicationContext());
|
||||
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) mvcConfiguration.resourceHandlerMapping();
|
||||
|
||||
AbstractHandlerMapping handlerMapping = this.wac.getBean(
|
||||
"resourceHandlerMapping", AbstractHandlerMapping.class);
|
||||
|
||||
assertNotNull(handlerMapping);
|
||||
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
|
||||
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
|
||||
|
@ -119,8 +130,10 @@ public class WebMvcConfigurationSupportTests {
|
|||
|
||||
@Test
|
||||
public void emptyDefaultServletHandlerMapping() {
|
||||
mvcConfiguration.setServletContext(new MockServletContext());
|
||||
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) mvcConfiguration.defaultServletHandlerMapping();
|
||||
|
||||
AbstractHandlerMapping handlerMapping = this.wac.getBean(
|
||||
"defaultServletHandlerMapping", AbstractHandlerMapping.class);
|
||||
|
||||
assertNotNull(handlerMapping);
|
||||
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
|
||||
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
|
||||
|
@ -128,11 +141,10 @@ public class WebMvcConfigurationSupportTests {
|
|||
|
||||
@Test
|
||||
public void requestMappingHandlerAdapter() throws Exception {
|
||||
RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter();
|
||||
|
||||
List<HttpMessageConverter<?>> expectedConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
mvcConfiguration.addDefaultHttpMessageConverters(expectedConverters);
|
||||
assertEquals(expectedConverters.size(), adapter.getMessageConverters().size());
|
||||
RequestMappingHandlerAdapter adapter = this.wac.getBean(RequestMappingHandlerAdapter.class);
|
||||
|
||||
assertEquals(9, adapter.getMessageConverters().size());
|
||||
|
||||
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
|
||||
assertNotNull(initializer);
|
||||
|
@ -146,10 +158,27 @@ public class WebMvcConfigurationSupportTests {
|
|||
assertTrue(validator instanceof LocalValidatorFactoryBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mvcUrls() throws Exception {
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
|
||||
try {
|
||||
DateTime now = DateTime.now();
|
||||
MvcUrls mvcUrls = this.wac.getBean(MvcUrls.class);
|
||||
UriComponents uriComponents = mvcUrls.linkToMethodOn(controller(
|
||||
TestController.class).methodWithTwoPathVariables(1, now));
|
||||
|
||||
assertEquals("/foo/1/bar/" + ISODateTimeFormat.date().print(now), uriComponents.getPath());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlerExceptionResolver() throws Exception {
|
||||
|
||||
HandlerExceptionResolverComposite compositeResolver =
|
||||
(HandlerExceptionResolverComposite) mvcConfiguration.handlerExceptionResolver();
|
||||
this.wac.getBean("handlerExceptionResolver", HandlerExceptionResolverComposite.class);
|
||||
|
||||
assertEquals(0, compositeResolver.getOrder());
|
||||
|
||||
|
@ -164,12 +193,28 @@ public class WebMvcConfigurationSupportTests {
|
|||
}
|
||||
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean(name={"/testController"})
|
||||
public TestController testController() {
|
||||
return new TestController();
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class TestController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public void handle() {
|
||||
}
|
||||
|
||||
@RequestMapping("/foo/{id}/bar/{date}")
|
||||
public HttpEntity<Void> methodWithTwoPathVariables(@PathVariable Integer id,
|
||||
@DateTimeFormat(iso = ISO.DATE) @PathVariable DateTime date) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.AnnotationAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationMappingDiscoverer}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationMappingDiscovererUnitTests {
|
||||
|
||||
AnnotationMappingDiscoverer discoverer = new AnnotationMappingDiscoverer(
|
||||
RequestMapping.class);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullAnnotationType() {
|
||||
new AnnotationMappingDiscoverer((Class<? extends Annotation>) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullAnnotationAttribute() {
|
||||
new AnnotationMappingDiscoverer((AnnotationAttribute) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoversTypeLevelMapping() {
|
||||
assertThat(discoverer.getMapping(MyController.class), is("/type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoversMethodLevelMapping() throws Exception {
|
||||
Method method = MyController.class.getMethod("method");
|
||||
assertThat(discoverer.getMapping(method), is("/type/method"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullForNonExistentTypeLevelMapping() {
|
||||
assertThat(discoverer.getMapping(ControllerWithoutTypeLevelMapping.class),
|
||||
is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesMethodLevelMappingWithoutTypeLevelMapping() throws Exception {
|
||||
|
||||
Method method = ControllerWithoutTypeLevelMapping.class.getMethod("method");
|
||||
assertThat(discoverer.getMapping(method), is("/method"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesMethodLevelMappingWithSlashRootMapping() throws Exception {
|
||||
|
||||
Method method = SlashRootMapping.class.getMethod("method");
|
||||
assertThat(discoverer.getMapping(method), is("/method"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #46
|
||||
*/
|
||||
@Test
|
||||
public void treatsMissingMethodMappingAsEmptyMapping() throws Exception {
|
||||
|
||||
Method method = MyController.class.getMethod("noMethodMapping");
|
||||
assertThat(discoverer.getMapping(method), is("/type"));
|
||||
}
|
||||
|
||||
@RequestMapping("/type")
|
||||
interface MyController {
|
||||
|
||||
@RequestMapping("/method")
|
||||
void method();
|
||||
|
||||
@RequestMapping
|
||||
void noMethodMapping();
|
||||
}
|
||||
|
||||
interface ControllerWithoutTypeLevelMapping {
|
||||
|
||||
@RequestMapping("/method")
|
||||
void method();
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
interface SlashRootMapping {
|
||||
|
||||
@RequestMapping("/method")
|
||||
void method();
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.hypermedia.MvcUriComponentsBuilderUnitTests.PersonControllerImpl;
|
||||
import org.springframework.web.servlet.hypermedia.MvcUriComponentsBuilderUnitTests.PersonsAddressesController;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.servlet.hypermedia.MvcUriComponentsBuilder.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MvcUriComponentsBuilderFactory}.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MvcUriComponentsBuilderFactoryUnitTests extends TestUtils {
|
||||
|
||||
List<UriComponentsContributor> contributors = Collections.emptyList();
|
||||
|
||||
MvcUriComponentsBuilderFactory factory = new MvcUriComponentsBuilderFactory(
|
||||
contributors);
|
||||
|
||||
@Test
|
||||
public void createsLinkToControllerRoot() {
|
||||
|
||||
URI link = factory.from(PersonControllerImpl.class).build().toUri();
|
||||
|
||||
assertPointsToMockServer(link);
|
||||
assertThat(link.toString(), endsWith("/people"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsLinkToParameterizedControllerRoot() {
|
||||
|
||||
URI link = factory.from(PersonsAddressesController.class, 15).build().toUri();
|
||||
|
||||
assertPointsToMockServer(link);
|
||||
assertThat(link.toString(), endsWith("/people/15/addresses"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appliesParameterValueIfContributorConfigured() {
|
||||
|
||||
List<? extends UriComponentsContributor> contributors = Arrays.asList(new SampleUriComponentsContributor());
|
||||
MvcUriComponentsBuilderFactory factory = new MvcUriComponentsBuilderFactory(
|
||||
contributors);
|
||||
|
||||
SpecialType specialType = new SpecialType();
|
||||
specialType.parameterValue = "value";
|
||||
|
||||
URI link = factory.from(
|
||||
methodOn(SampleController.class).sampleMethod(1L, specialType)).build().toUri();
|
||||
assertPointsToMockServer(link);
|
||||
assertThat(link.toString(), endsWith("/sample/1?foo=value"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #57
|
||||
*/
|
||||
@Test
|
||||
public void usesDateTimeFormatForUriBinding() {
|
||||
|
||||
DateTime now = DateTime.now();
|
||||
|
||||
MvcUriComponentsBuilderFactory factory = new MvcUriComponentsBuilderFactory(
|
||||
contributors);
|
||||
URI link = factory.from(methodOn(SampleController.class).sampleMethod(now)).build().toUri();
|
||||
assertThat(link.toString(),
|
||||
endsWith("/sample/" + ISODateTimeFormat.date().print(now)));
|
||||
}
|
||||
|
||||
static interface SampleController {
|
||||
|
||||
@RequestMapping("/sample/{id}")
|
||||
HttpEntity<?> sampleMethod(@PathVariable("id") Long id, SpecialType parameter);
|
||||
|
||||
@RequestMapping("/sample/{time}")
|
||||
HttpEntity<?> sampleMethod(
|
||||
@PathVariable("time") @DateTimeFormat(iso = ISO.DATE) DateTime time);
|
||||
}
|
||||
|
||||
static class SampleUriComponentsContributor implements UriComponentsContributor {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return SpecialType.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enhance(UriComponentsBuilder builder, MethodParameter parameter,
|
||||
Object value) {
|
||||
builder.queryParam("foo", ((SpecialType) value).parameterValue);
|
||||
}
|
||||
}
|
||||
|
||||
static class SpecialType {
|
||||
|
||||
String parameterValue;
|
||||
}
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.servlet.hypermedia.MvcUriComponentsBuilder.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MvcUriComponentsBuilder}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
*/
|
||||
public class MvcUriComponentsBuilderUnitTests extends TestUtils {
|
||||
|
||||
@Test
|
||||
public void createsLinkToControllerRoot() {
|
||||
|
||||
URI link = from(PersonControllerImpl.class).build().toUri();
|
||||
assertThat(link.toString(), Matchers.endsWith("/people"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsLinkToParameterizedControllerRoot() {
|
||||
|
||||
URI link = from(PersonsAddressesController.class, 15).build().toUri();
|
||||
assertThat(link.toString(), endsWith("/people/15/addresses"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #70
|
||||
*/
|
||||
@Test
|
||||
public void createsLinkToMethodOnParameterizedControllerRoot() {
|
||||
|
||||
URI link = from(
|
||||
methodOn(PersonsAddressesController.class, 15).getAddressesForCountry(
|
||||
"DE")).build().toUri();
|
||||
assertThat(link.toString(), endsWith("/people/15/addresses/DE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsLinkToSubResource() {
|
||||
|
||||
URI link = from(PersonControllerImpl.class).pathSegment("something").build().toUri();
|
||||
assertThat(link.toString(), endsWith("/people/something"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsControllerWithMultipleMappings() {
|
||||
from(InvalidController.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsLinkToUnmappedController() {
|
||||
|
||||
URI link = from(UnmappedController.class).build().toUri();
|
||||
assertThat(link.toString(), is("http://localhost/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendingNullIsANoOp() {
|
||||
|
||||
URI link = from(PersonControllerImpl.class).path(null).build().toUri();
|
||||
assertThat(link.toString(), endsWith("/people"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksToMethod() {
|
||||
|
||||
URI link = from(methodOn(ControllerWithMethods.class).myMethod(null)).build().toUri();
|
||||
assertPointsToMockServer(link);
|
||||
assertThat(link.toString(), endsWith("/something/else"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksToMethodWithPathVariable() {
|
||||
|
||||
URI link = from(methodOn(ControllerWithMethods.class).methodWithPathVariable("1")).build().toUri();
|
||||
assertPointsToMockServer(link);
|
||||
assertThat(link.toString(), endsWith("/something/1/foo"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #33
|
||||
*/
|
||||
@Test
|
||||
public void usesForwardedHostAsHostIfHeaderIsSet() {
|
||||
|
||||
request.addHeader("X-Forwarded-Host", "somethingDifferent");
|
||||
|
||||
URI link = from(PersonControllerImpl.class).build().toUri();
|
||||
assertThat(link.toString(), startsWith("http://somethingDifferent"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #26, #39
|
||||
*/
|
||||
@Test
|
||||
public void linksToMethodWithPathVariableAndRequestParams() {
|
||||
|
||||
URI link = from(
|
||||
methodOn(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build().toUri();
|
||||
|
||||
UriComponents components = toComponents(link);
|
||||
assertThat(components.getPath(), is("/something/1/foo"));
|
||||
|
||||
MultiValueMap<String, String> queryParams = components.getQueryParams();
|
||||
assertThat(queryParams.get("limit"), contains("5"));
|
||||
assertThat(queryParams.get("offset"), contains("10"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #26, #39
|
||||
*/
|
||||
@Test
|
||||
public void linksToMethodWithPathVariableAndMultiValueRequestParams() {
|
||||
|
||||
URI link = from(
|
||||
methodOn(ControllerWithMethods.class).methodWithMultiValueRequestParams(
|
||||
"1", Arrays.asList(3, 7), 5)).build().toUri();
|
||||
|
||||
UriComponents components = toComponents(link);
|
||||
assertThat(components.getPath(), is("/something/1/foo"));
|
||||
|
||||
MultiValueMap<String, String> queryParams = components.getQueryParams();
|
||||
assertThat(queryParams.get("limit"), contains("5"));
|
||||
assertThat(queryParams.get("items"), containsInAnyOrder("3", "7"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #90
|
||||
*/
|
||||
@Test
|
||||
public void usesForwardedHostAndPortFromHeader() {
|
||||
|
||||
request.addHeader("X-Forwarded-Host", "foobar:8088");
|
||||
|
||||
URI link = from(PersonControllerImpl.class).build().toUri();
|
||||
assertThat(link.toString(), startsWith("http://foobar:8088"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #90
|
||||
*/
|
||||
@Test
|
||||
public void usesFirstHostOfXForwardedHost() {
|
||||
|
||||
request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088");
|
||||
|
||||
URI link = from(PersonControllerImpl.class).build().toUri();
|
||||
assertThat(link.toString(), startsWith("http://barfoo:8888"));
|
||||
}
|
||||
|
||||
private static UriComponents toComponents(URI link) {
|
||||
return UriComponentsBuilder.fromUri(link).build();
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/people")
|
||||
interface PersonController {
|
||||
|
||||
}
|
||||
|
||||
class PersonControllerImpl implements PersonController {
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/people/{id}/addresses")
|
||||
static class PersonsAddressesController {
|
||||
|
||||
@RequestMapping("/{country}")
|
||||
public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping({ "/persons", "/people" })
|
||||
class InvalidController {
|
||||
|
||||
}
|
||||
|
||||
class UnmappedController {
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/something")
|
||||
static class ControllerWithMethods {
|
||||
|
||||
@RequestMapping("/else")
|
||||
HttpEntity<Void> myMethod(@RequestBody Object payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping("/{id}/foo")
|
||||
HttpEntity<Void> methodWithPathVariable(@PathVariable String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}/foo")
|
||||
HttpEntity<Void> methodForNextPage(@PathVariable String id,
|
||||
@RequestParam Integer offset, @RequestParam Integer limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}/foo")
|
||||
HttpEntity<Void> methodWithMultiValueRequestParams(@PathVariable String id,
|
||||
@RequestParam List<Integer> items, @RequestParam Integer limit) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RecordedInvocationUtilsUnitTests extends TestUtils {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
MvcUriComponentsBuilder.from(RecordedInvocationUtils.methodOn(SampleController.class).someMethod(
|
||||
1L));
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/sample")
|
||||
static class SampleController {
|
||||
|
||||
@RequestMapping("/{id}/foo")
|
||||
HttpEntity<Void> someMethod(@PathVariable("id") Long id) {
|
||||
return new ResponseEntity<Void>(HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.hypermedia;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Utility class to ease tesing.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class TestUtils {
|
||||
|
||||
protected MockHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
}
|
||||
|
||||
protected void assertPointsToMockServer(URI link) {
|
||||
assertThat(link.toString(), startsWith("http://localhost"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultMvcUrls}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class DefaultMvcUrlsTests {
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MvcUrls mvcUrls;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
|
||||
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(new PathVariableMethodArgumentResolver());
|
||||
resolvers.add(new RequestParamMethodArgumentResolver(null, false));
|
||||
|
||||
this.mvcUrls = new DefaultMvcUrls(resolvers, null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void linkToControllerRoot() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build();
|
||||
assertThat(uriComponents.toUriString(), Matchers.endsWith("/people"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToParameterizedControllerRoot() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(
|
||||
PersonsAddressesController.class).buildAndExpand(15);
|
||||
|
||||
assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodOnParameterizedControllerRoot() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethodOn(
|
||||
controller(PersonsAddressesController.class, 15).getAddressesForCountry("DE"));
|
||||
|
||||
assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToSubResource() {
|
||||
UriComponents uriComponents =
|
||||
this.mvcUrls.linkToController(PersonControllerImpl.class).pathSegment("something").build();
|
||||
|
||||
assertThat(uriComponents.toUriString(), endsWith("/people/something"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToControllerWithMultipleMappings() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(InvalidController.class).build();
|
||||
assertThat(uriComponents.toUriString(), is("http://localhost/persons"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToControllerNotMapped() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(UnmappedController.class).build();
|
||||
assertThat(uriComponents.toUriString(), is("http://localhost/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodRefWithPathVar() throws Exception {
|
||||
Method method = ControllerWithMethods.class.getDeclaredMethod("methodWithPathVariable", String.class);
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] { "1" });
|
||||
|
||||
assertThat(uriComponents.toUriString(), is("http://localhost/something/1/foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodRefWithTwoPathVars() throws Exception {
|
||||
DateTime now = DateTime.now();
|
||||
Method method = ControllerWithMethods.class.getDeclaredMethod(
|
||||
"methodWithTwoPathVariables", Integer.class, DateTime.class);
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] { 1, now });
|
||||
|
||||
assertThat(uriComponents.getPath(), is("/something/1/foo/" + ISODateTimeFormat.date().print(now)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodRefWithPathVarAndRequestParam() throws Exception {
|
||||
Method method = ControllerWithMethods.class.getDeclaredMethod("methodForNextPage", String.class, Integer.class, Integer.class);
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] {"1", 10, 5});
|
||||
|
||||
assertThat(uriComponents.getPath(), is("/something/1/foo"));
|
||||
|
||||
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
|
||||
assertThat(queryParams.get("limit"), contains("5"));
|
||||
assertThat(queryParams.get("offset"), contains("10"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethod() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethodOn(
|
||||
controller(ControllerWithMethods.class).myMethod(null));
|
||||
|
||||
assertThat(uriComponents.toUriString(), startsWith("http://localhost"));
|
||||
assertThat(uriComponents.toUriString(), endsWith("/something/else"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodWithPathVar() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethodOn(
|
||||
controller(ControllerWithMethods.class).methodWithPathVariable("1"));
|
||||
|
||||
assertThat(uriComponents.toUriString(), startsWith("http://localhost"));
|
||||
assertThat(uriComponents.toUriString(), endsWith("/something/1/foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodWithPathVarAndRequestParams() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethodOn(
|
||||
controller(ControllerWithMethods.class).methodForNextPage("1", 10, 5));
|
||||
|
||||
assertThat(uriComponents.getPath(), is("/something/1/foo"));
|
||||
|
||||
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
|
||||
assertThat(queryParams.get("limit"), contains("5"));
|
||||
assertThat(queryParams.get("offset"), contains("10"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linkToMethodWithPathVarAndMultiValueRequestParams() {
|
||||
UriComponents uriComponents = this.mvcUrls.linkToMethodOn(
|
||||
controller(ControllerWithMethods.class).methodWithMultiValueRequestParams(
|
||||
"1", Arrays.asList(3, 7), 5));
|
||||
|
||||
assertThat(uriComponents.getPath(), is("/something/1/foo"));
|
||||
|
||||
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
|
||||
assertThat(queryParams.get("limit"), contains("5"));
|
||||
assertThat(queryParams.get("items"), containsInAnyOrder("3", "7"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesForwardedHostAsHostIfHeaderIsSet() {
|
||||
this.request.addHeader("X-Forwarded-Host", "somethingDifferent");
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build();
|
||||
|
||||
assertThat(uriComponents.toUriString(), startsWith("http://somethingDifferent"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesForwardedHostAndPortFromHeader() {
|
||||
request.addHeader("X-Forwarded-Host", "foobar:8088");
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build();
|
||||
|
||||
assertThat(uriComponents.toUriString(), startsWith("http://foobar:8088"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesFirstHostOfXForwardedHost() {
|
||||
request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088");
|
||||
UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build();
|
||||
|
||||
assertThat(uriComponents.toUriString(), startsWith("http://barfoo:8888"));
|
||||
}
|
||||
|
||||
|
||||
static class Person {
|
||||
|
||||
Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/people")
|
||||
interface PersonController {
|
||||
|
||||
}
|
||||
|
||||
class PersonControllerImpl implements PersonController {
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/people/{id}/addresses")
|
||||
static class PersonsAddressesController {
|
||||
|
||||
@RequestMapping("/{country}")
|
||||
public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping({ "/persons", "/people" })
|
||||
class InvalidController {
|
||||
|
||||
}
|
||||
|
||||
class UnmappedController {
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/something")
|
||||
static class ControllerWithMethods {
|
||||
|
||||
@RequestMapping("/else")
|
||||
HttpEntity<Void> myMethod(@RequestBody Object payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping("/{id}/foo")
|
||||
HttpEntity<Void> methodWithPathVariable(@PathVariable String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping("/{id}/foo/{date}")
|
||||
HttpEntity<Void> methodWithTwoPathVariables(
|
||||
@PathVariable Integer id, @DateTimeFormat(iso = ISO.DATE) @PathVariable DateTime date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}/foo")
|
||||
HttpEntity<Void> methodForNextPage(@PathVariable String id,
|
||||
@RequestParam Integer offset, @RequestParam Integer limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}/foo")
|
||||
HttpEntity<Void> methodWithMultiValueRequestParams(@PathVariable String id,
|
||||
@RequestParam List<Integer> items, @RequestParam Integer limit) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.support.MvcUrlUtils.ControllerMethodValues;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link MvcUrlUtils}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MvcUrlUtilsTests {
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodOn() {
|
||||
HttpEntity<Void> result = MvcUrlUtils.controller(SampleController.class).someMethod(1L);
|
||||
|
||||
assertTrue(result instanceof ControllerMethodValues);
|
||||
assertEquals("someMethod", ((ControllerMethodValues) result).getControllerMethod().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeLevelMapping() {
|
||||
assertThat(MvcUrlUtils.getTypeLevelMapping(MyController.class), is("/type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeLevelMappingNone() {
|
||||
assertThat(MvcUrlUtils.getTypeLevelMapping(ControllerWithoutTypeLevelMapping.class), is("/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodLevelMapping() throws Exception {
|
||||
Method method = MyController.class.getMethod("method");
|
||||
assertThat(MvcUrlUtils.getMethodMapping(method), is("/type/method"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodLevelMappingWithoutTypeLevelMapping() throws Exception {
|
||||
Method method = ControllerWithoutTypeLevelMapping.class.getMethod("method");
|
||||
assertThat(MvcUrlUtils.getMethodMapping(method), is("/method"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMappingWithControllerMappingOnly() throws Exception {
|
||||
Method method = MyController.class.getMethod("noMethodMapping");
|
||||
assertThat(MvcUrlUtils.getMethodMapping(method), is("/type"));
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping("/sample")
|
||||
static class SampleController {
|
||||
|
||||
@RequestMapping("/{id}/foo")
|
||||
HttpEntity<Void> someMethod(@PathVariable("id") Long id) {
|
||||
return new ResponseEntity<Void>(HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/type")
|
||||
interface MyController {
|
||||
|
||||
@RequestMapping("/method")
|
||||
void method();
|
||||
|
||||
@RequestMapping
|
||||
void noMethodMapping();
|
||||
}
|
||||
|
||||
interface ControllerWithoutTypeLevelMapping {
|
||||
|
||||
@RequestMapping("/method")
|
||||
void method();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue