Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
Expand All @@ -69,7 +70,7 @@

/**
* Creates instances of {@link org.springframework.web.util.UriComponentsBuilder}
* by pointing to {@code @RequestMapping} methods on Spring MVC controllers.
* by pointing to {@code @RequestMapping} or {@code @HttpExchange} methods on Spring MVC controllers.
*
* <p>There are several groups of methods:
* <ul>
Expand All @@ -94,6 +95,7 @@
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Juergen Hoeller
* @author Daeho Kwon
* @since 4.0
*/
public class MvcUriComponentsBuilder {
Expand Down Expand Up @@ -326,10 +328,10 @@ public static UriComponentsBuilder fromMethodCall(UriComponentsBuilder builder,
}

/**
* Return a "mock" controller instance. When an {@code @RequestMapping} method
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create a {@code UriComponentsBuilder}
* via {@link #fromMethodCall(Object)}.
* Return a "mock" controller instance. When an {@code @RequestMapping} or
* {@code @HttpExchange} method on the controller is invoked, the supplied
* argument values are remembered and the result can then be used to create a
* {@code UriComponentsBuilder} via {@link #fromMethodCall(Object)}.
* <p>Note that this is a shorthand version of {@link #controller(Class)} intended
* for inline use (with a static import), for example:
* <pre class="code">
Expand All @@ -342,10 +344,10 @@ public static <T> T on(Class<T> controllerType) {
}

/**
* Return a "mock" controller instance. When an {@code @RequestMapping} method
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create {@code UriComponentsBuilder} via
* {@link #fromMethodCall(Object)}.
* Return a "mock" controller instance. When an {@code @RequestMapping} or
* {@code @HttpExchange} method on the controller is invoked, the supplied
* argument values are remembered and the result can then be used to create
* {@code UriComponentsBuilder} via {@link #fromMethodCall(Object)}.
* <p>This is a longer version of {@link #on(Class)}. It is needed with controller
* methods returning void as well for repeated invocations.
* <pre class="code">
Expand Down Expand Up @@ -543,18 +545,27 @@ private static String getPathPrefix(Class<?> controllerType) {
private static String getClassMapping(Class<?> controllerType) {
Assert.notNull(controllerType, "'controllerType' must not be null");
RequestMapping mapping = AnnotatedElementUtils.findMergedAnnotation(controllerType, RequestMapping.class);
if (mapping == null) {
return "";
if (mapping != null) {
return getPathMapping(mapping, controllerType.getName());
}
HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(controllerType, HttpExchange.class);
if (httpExchange != null) {
String url = httpExchange.url();
return StringUtils.hasText(url) ? url : "";
}
return getPathMapping(mapping, controllerType.getName());
return "";
}

private static String getMethodMapping(AnnotatedMethod annotatedMethod) {
RequestMapping requestMapping = annotatedMethod.getMethodAnnotation(RequestMapping.class);
if (requestMapping == null) {
throw new IllegalArgumentException("No @RequestMapping on: " + annotatedMethod.getMethod().toGenericString());
if (requestMapping != null) {
return getPathMapping(requestMapping, annotatedMethod.getMethod().toGenericString());
}
HttpExchange httpExchange = annotatedMethod.getMethodAnnotation(HttpExchange.class);
if (httpExchange != null) {
return httpExchange.url();
}
return getPathMapping(requestMapping, annotatedMethod.getMethod().toGenericString());
throw new IllegalArgumentException("No @RequestMapping or @HttpExchange on: " + annotatedMethod.getMethod().toGenericString());
}

private static String getPathMapping(RequestMapping requestMapping, String source) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.ForwardedHeaderFilter;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
Expand Down Expand Up @@ -84,6 +86,7 @@
* @author Dietrich Schulten
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Daeho Kwon
*/
@SuppressWarnings("unused")
class MvcUriComponentsBuilderTests {
Expand Down Expand Up @@ -357,6 +360,42 @@ void fromMethodNameWithAnnotationsOnInterface() {
assertThat(uriComponents.toString()).isEqualTo("http://localhost/hello/test");
}

@Test
void fromControllerWithHttpExchange() {
UriComponents uriComponents = fromController(PersonHttpExchangeController.class).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/people");
}

@Test
void fromMethodNameWithHttpExchange() {
UriComponents uriComponents = fromMethodName(PersonHttpExchangeController.class, "getPerson", 123L).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/people/123");
}

@Test
void fromMethodCallWithHttpExchange() {
UriComponents uriComponents = fromMethodCall(on(PersonHttpExchangeController.class).getPerson(123L)).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/people/123");
}

@Test
void fromControllerWithHttpExchangeOnInterface() {
UriComponents uriComponents = fromController(PersonHttpExchangeControllerImpl.class).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/persons");
}

@Test
void fromMethodNameWithHttpExchangeOnInterface() {
UriComponents uriComponents = fromMethodName(PersonHttpExchangeControllerImpl.class, "getPerson", 123L).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/persons/123");
}

@Test
void fromMethodCallWithHttpExchangeOnInterface() {
UriComponents uriComponents = fromMethodCall(on(PersonHttpExchangeControllerImpl.class).getPerson(123L)).build();
assertThat(uriComponents.toUriString()).endsWith("/exchange/persons/123");
}

@Test
void fromMethodCallOnSubclass() {
UriComponents uriComponents = fromMethodCall(on(ExtendedController.class).myMethod(null)).build();
Expand Down Expand Up @@ -900,4 +939,33 @@ public ResponseEntity<String> get(String name) {
}
}


@Controller
@HttpExchange("/exchange/people")
static class PersonHttpExchangeController {

@GetExchange("/{id}")
HttpEntity<Void> getPerson(@PathVariable Long id) {
return null;
}
}


@HttpExchange("/exchange/persons")
interface PersonHttpExchangeInterface {

@GetExchange("/{id}")
HttpEntity<Void> getPerson(@PathVariable Long id);
}


@Controller
static class PersonHttpExchangeControllerImpl implements PersonHttpExchangeInterface {

@Override
public HttpEntity<Void> getPerson(Long id) {
return null;
}
}

}