From 4b9007ba3bf557a7429d6c89cbfb8a35a895a042 Mon Sep 17 00:00:00 2001 From: Gmugra Date: Sat, 6 Mar 2021 11:29:35 +0100 Subject: [PATCH] #27 : Context-annotation should support javax.ws.rs.core.UriInfo --- README.md | 1 + .../invoke/MethodParameterFactory.java | 4 + .../routing/invoke/UriInfoParameter.java | 25 ++ .../cactusthorn/routing/uri/PathTemplate.java | 15 + .../cactusthorn/routing/uri/UriInfoImpl.java | 277 ++++++++++++++++++ .../routing/RoutingServletTest.java | 19 +- .../routing/invoke/UriInfoParameterTest.java | 52 ++++ .../routing/uri/UriInfoImplTest.java | 249 ++++++++++++++++ .../demo/jetty/resource/SimpleResource.java | 6 + .../src/main/resources/thymeleaf/index.html | 2 + 10 files changed, 648 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/net/cactusthorn/routing/invoke/UriInfoParameter.java create mode 100644 core/src/main/java/net/cactusthorn/routing/uri/UriInfoImpl.java create mode 100644 core/src/test/java/net/cactusthorn/routing/invoke/UriInfoParameterTest.java create mode 100644 core/src/test/java/net/cactusthorn/routing/uri/UriInfoImplTest.java diff --git a/README.md b/README.md index ae00518..3632f36 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ The type of the annotated parameter must either: * javax.servlet.ServletContext * javax.ws.rs.core.SecurityContext * javax.ws.rs.core.HttpHeaders + * javax.ws.rs.core.UriInfo 1. `javax.ws.rs.core.Response` 1. `javax.ws.rs.ext.ExceptionMapper` 1. `javax.ws.rs.ext.ParamConverterProvider` diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameterFactory.java b/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameterFactory.java index d5c0251..a17c17c 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameterFactory.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameterFactory.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; import javax.ws.rs.CookieParam; import javax.ws.rs.QueryParam; import javax.ws.rs.HeaderParam; @@ -84,6 +85,9 @@ static MethodParameter create(Method method, Parameter parameter, Type genericTy if (HttpHeaders.class == parameter.getType()) { return new HttpHeadersParameter(method, parameter, genericType, position); } + if (UriInfo.class == parameter.getType()) { + return new UriInfoParameter(method, parameter, genericType, position); + } throw new RoutingInitializationException(Messages.msg(CONTEXT_NOT_SUPPORTED, parameter.getType(), method)); } if (method.getAnnotation(POST.class) != null || method.getAnnotation(PUT.class) != null diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/UriInfoParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/UriInfoParameter.java new file mode 100644 index 0000000..dd92f72 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/invoke/UriInfoParameter.java @@ -0,0 +1,25 @@ +package net.cactusthorn.routing.invoke; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.UriInfo; + +import net.cactusthorn.routing.uri.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.UriInfoImpl; + +public class UriInfoParameter extends MethodParameter { + + public UriInfoParameter(Method method, Parameter parameter, Type genericType, int position) { + super(method, parameter, genericType, position); + } + + @Override // + public UriInfo findValue(HttpServletRequest req, HttpServletResponse res, ServletContext con, PathValues pathValues) throws Exception { + return new UriInfoImpl(req, pathValues); + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java b/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java index 88217e9..398038d 100644 --- a/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java +++ b/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java @@ -6,7 +6,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + import net.cactusthorn.routing.util.Messages; +import net.cactusthorn.routing.util.UnmodifiableMultivaluedMap; + import static net.cactusthorn.routing.util.Messages.Key.ERROR_MULTIPLE_TEMPLATE_PARAM; public final class PathTemplate extends Template { @@ -31,6 +36,16 @@ public void put(String name, String value) { public String value(String name) { return values.get(name); } + + public MultivaluedMap toMultivaluedMap(boolean decode) { + MultivaluedMap map = new MultivaluedHashMap<>(); + if (decode) { + values.entrySet().forEach(e -> map.addFirst(e.getKey(), e.getValue())); + } else { + values.entrySet().forEach(e -> map.addFirst(e.getKey(), UriComponentEncoder.PATH.encode(e.getValue()))); + } + return new UnmodifiableMultivaluedMap<>(map); + } } public static final Comparator COMPARATOR = (o1, o2) -> { diff --git a/core/src/main/java/net/cactusthorn/routing/uri/UriInfoImpl.java b/core/src/main/java/net/cactusthorn/routing/uri/UriInfoImpl.java new file mode 100644 index 0000000..9854908 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/uri/UriInfoImpl.java @@ -0,0 +1,277 @@ +package net.cactusthorn.routing.uri; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.PathSegment; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.RuntimeDelegate; + +import net.cactusthorn.routing.uri.PathTemplate.PathValues; +import net.cactusthorn.routing.util.UnmodifiableMultivaluedMap; + +public class UriInfoImpl implements UriInfo { + + private final URI baseURI; + private final URI absolutePath; + private final URI requestURI; + private final URI path; + private final String queryString; + private final PathValues pathValues; + private MultivaluedMap decodedQueryParameters; + private MultivaluedMap encodedQueryParameters; + + public UriInfoImpl(HttpServletRequest request, PathValues pathValues) { + this.pathValues = pathValues; + baseURI = createBaseURI(request); + path = findPath(request); + queryString = request.getQueryString(); + absolutePath = getBaseUri().resolve(getPath()); + if (queryString == null) { + requestURI = absolutePath; + } else { + try { + requestURI = new URI(absolutePath.getScheme(), absolutePath.getAuthority(), absolutePath.getPath(), queryString, null); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } + + decodedQueryParameters = new MultivaluedHashMap<>(); + encodedQueryParameters = new MultivaluedHashMap<>(); + for (String parameter : Collections.list(request.getParameterNames())) { + String[] values = request.getParameterValues(parameter); + decodedQueryParameters.addAll(parameter, values); + for (String value : values) { + encodedQueryParameters.add(UriComponentEncoder.QUERY_PARAM.encode(parameter), + UriComponentEncoder.QUERY_PARAM.encode(value)); + } + } + decodedQueryParameters = new UnmodifiableMultivaluedMap<>(decodedQueryParameters); + encodedQueryParameters = new UnmodifiableMultivaluedMap<>(encodedQueryParameters); + } + + private static final String SCHEME_SEPARATOR = "://"; + + // schema://authority/ContextPath/ServletPath/ + private URI createBaseURI(HttpServletRequest request) { + try { + String basePath = findBasePath(request); + String xForwardedHost = request.getHeader("X-Forwarded-Host"); // X-Forwarded-Host: + if (xForwardedHost != null) { + return new URI(findScheme(request) + SCHEME_SEPARATOR + xForwardedHost + basePath); + } + String hostHeader = request.getHeader(HttpHeaders.HOST); // Host: : + if (hostHeader != null) { + return new URI(findScheme(request) + SCHEME_SEPARATOR + hostHeader + basePath); + } + return new URI(findScheme(request), null, request.getLocalAddr(), request.getLocalPort(), basePath, null, null); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } + + /** + * @return /ContextPath/ServletPath/ + */ + private String findBasePath(HttpServletRequest request) { + String result = request.getContextPath(); + if (!result.endsWith("/")) { + result += '/'; + } + if (!result.startsWith("/")) { + result = '/' + result; + } + + String servletPath = request.getServletPath(); + if (!"".equals(servletPath)) { + result += servletPath.substring(1); // This path starts with a "/" character + if (!result.endsWith("/")) { + result += '/'; + } + } + return result; + } + + private String findScheme(HttpServletRequest request) { + String xForwardedProto = request.getHeader("X-Forwarded-Proto"); + if (xForwardedProto != null) { + return xForwardedProto; + } + return request.isSecure() ? "https" : "http"; + } + + private URI findPath(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + if (pathInfo == null || pathInfo.isEmpty() || "/".equals(pathInfo)) { + return URI.create(""); + } + if (pathInfo.startsWith("/")) { + return URI.create(pathInfo.substring(1)); + } + return URI.create(pathInfo); + } + + /** + * Get the path of the current request relative to the base URI as a string. All + * sequences of escaped octets are decoded, equivalent to getPath(true). + */ + @Override public String getPath() { + return getPath(true); + } + + @Override public String getPath(boolean decode) { + return decode ? path.toString() : UriComponentEncoder.PATH.encode(path.toString()); + } + + @Override public List getPathSegments() { + throw new UnsupportedOperationException(); + } + + @Override public List getPathSegments(boolean decode) { + throw new UnsupportedOperationException(); + } + + /** + * Get the absolute request URI including any query parameters. + */ + @Override public URI getRequestUri() { + return requestURI; + } + + /** + * Get the absolute request URI in the form of a UriBuilder. + */ + @Override public UriBuilder getRequestUriBuilder() { + return UriBuilder.fromUri(getRequestUri()); + } + + /** + * Get the absolute path of the request. This includes everything preceding the + * path (host, port etc) but excludes query parameters. This is a shortcut for + * uriInfo.getBaseUri().resolve(uriInfo.getPath(false)). + */ + @Override public URI getAbsolutePath() { + return absolutePath; + } + + /** + * Get the absolute path of the request in the form of a UriBuilder. This + * includes everything preceding the path (host, port etc) but excludes query + * parameters. + */ + @Override public UriBuilder getAbsolutePathBuilder() { + return UriBuilder.fromUri(getAbsolutePath()); + } + + /** + * Get the base URI of the application. URIs of root resource classes are all + * relative to this base URI. + */ + @Override public URI getBaseUri() { + return baseURI; + } + + /** + * Get the base URI of the application in the form of a UriBuilder. + */ + @Override public UriBuilder getBaseUriBuilder() { + return UriBuilder.fromUri(getBaseUri()); + } + + @Override public MultivaluedMap getPathParameters() { + return getPathParameters(true); + } + + @Override public MultivaluedMap getPathParameters(boolean decode) { + return pathValues.toMultivaluedMap(decode); + } + + @Override public MultivaluedMap getQueryParameters() { + return getQueryParameters(true); + } + + @Override public MultivaluedMap getQueryParameters(boolean decode) { + if (decode) { + return decodedQueryParameters; + } + return encodedQueryParameters; + } + + @Override public List getMatchedURIs() { + throw new UnsupportedOperationException(); + } + + @Override public List getMatchedURIs(boolean decode) { + throw new UnsupportedOperationException(); + } + + @Override public List getMatchedResources() { + throw new UnsupportedOperationException(); + } + + /** + * Resolve a relative URI with respect to the base URI of the application. The + * resolved URI returned by this method is normalized. If the supplied URI is + * already resolved, it is just returned. + */ + @Override public URI resolve(URI uri) { + return getBaseUri().resolve(uri); + } + + /** + * Relativize a URI with respect to the current request URI. Relativization + * works as follows: + * + * 1. If the URI to relativize is already relative, it is first resolved using + * resolve(java.net.URI). 2. The resulting URI is relativized with respect to + * the current request URI. If the two URIs do not share a prefix, the URI + * computed in step 1 is returned. + */ + @Override public URI relativize(URI uri) { + URI to = uri; + if (!to.isAbsolute()) { + to = resolve(uri); + } + if (to.isOpaque() || !getBaseUri().getScheme().equals(to.getScheme()) || !getBaseUri().getAuthority().equals(to.getAuthority())) { + return to; + } + + String[] toSegments = pathSegments(to); + String[] requestSegments = pathSegments(getRequestUri()); + + UriBuilder builder = RuntimeDelegate.getInstance().createUriBuilder(); + builder.replaceQuery(to.getQuery()); + builder.fragment(to.getFragment()); + + int position = 0; + while (position < toSegments.length && position < requestSegments.length) { + if (!toSegments[position].equals(requestSegments[position])) { + break; + } + position++; + } + if (position < toSegments.length) { + builder.segment(Arrays.copyOfRange(toSegments, position, toSegments.length)); + } + + return builder.build(); + } + + private String[] pathSegments(URI uri) { + String uriPath = uri.getPath(); + if (uriPath.startsWith("/")) { + uriPath = uriPath.substring(1); + } + return uriPath.split("/"); + } + +} diff --git a/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java b/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java index 1d7ca30..bae50b7 100644 --- a/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java +++ b/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java @@ -9,6 +9,11 @@ import java.util.Collections; import java.util.List; +import java.util.logging.Logger; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; + import javax.annotation.security.RolesAllowed; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -31,6 +36,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -165,6 +171,16 @@ public Object provide(Class clazz, HttpServletRequest request) { HttpServletResponse resp; ServletTestOutputStream outputStream; + @BeforeAll // + static void setUpLogger() { + Logger rootLogger = LogManager.getLogManager().getLogger(""); + rootLogger.setLevel(Level.FINE); + //switch off default Handlers to do not get anything in console + for (Handler h : rootLogger.getHandlers()) { + h.setLevel(Level.OFF); + } + } + @BeforeEach // void setUp() throws IOException { req = Mockito.mock(HttpServletRequest.class); @@ -420,8 +436,7 @@ public void userRoles() throws ServletException, IOException { assertEquals(403, code.getValue()); } - @Test - public void wrongProduces() throws ServletException, IOException { + @Test public void wrongProduces() throws ServletException, IOException { Mockito.when(req.getPathInfo()).thenReturn("/api/wrong/produces"); Mockito.when(req.getMethod()).thenReturn(HttpMethod.GET); Mockito.when(req.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaType.TEXT_HTML); diff --git a/core/src/test/java/net/cactusthorn/routing/invoke/UriInfoParameterTest.java b/core/src/test/java/net/cactusthorn/routing/invoke/UriInfoParameterTest.java new file mode 100644 index 0000000..5e76e92 --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/invoke/UriInfoParameterTest.java @@ -0,0 +1,52 @@ +package net.cactusthorn.routing.invoke; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import net.cactusthorn.routing.ComponentProvider; +import net.cactusthorn.routing.RoutingConfig; + +import static net.cactusthorn.routing.uri.PathTemplate.PathValues; + +public class UriInfoParameterTest extends InvokeTestAncestor { + + public static class EntryPoint1 { + + public void simple(@Context UriInfo uriInfo) { + } + } + + public static class EntryPoint1Provider implements ComponentProvider { + + @Override // + public Object provide(Class clazz, HttpServletRequest request) { + return new EntryPoint1(); + } + } + + private static final RoutingConfig CONFIG = RoutingConfig.builder(new EntryPoint1Provider()).addResource(EntryPoint1.class).build(); + + @Test // + public void simple() throws Exception { + Mockito.when(request.getContextPath()).thenReturn("cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt"); + Mockito.when(request.getPathInfo()).thenReturn("/app/xxx"); + Mockito.when(request.getHeader("Host")).thenReturn("cactusthorn.net"); + Mockito.when(request.isSecure()).thenReturn(false); + Mockito.when(request.getParameterNames()).thenReturn(Collections.emptyEnumeration()); + + MethodParameter mp = parameterInfo(EntryPoint1.class, "simple", CONFIG); + + UriInfo uriInfo = (UriInfo) mp.findValue(request, null, null, new PathValues()); + assertEquals("http://cactusthorn.net/cntxt/srvlt/app/xxx", uriInfo.getAbsolutePath().toString()); + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/uri/UriInfoImplTest.java b/core/src/test/java/net/cactusthorn/routing/uri/UriInfoImplTest.java new file mode 100644 index 0000000..264865b --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/uri/UriInfoImplTest.java @@ -0,0 +1,249 @@ +package net.cactusthorn.routing.uri; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static net.cactusthorn.routing.uri.PathTemplate.PathValues; + +public class UriInfoImplTest { + + private HttpServletRequest request; + + @BeforeEach public void setUp() { + request = Mockito.mock(HttpServletRequest.class); + + Mockito.when(request.getContextPath()).thenReturn(""); + Mockito.when(request.getServletPath()).thenReturn(""); + Mockito.when(request.getPathInfo()).thenReturn(""); + Mockito.when(request.getHeader("Host")).thenReturn("cactusthorn.net"); + Mockito.when(request.isSecure()).thenReturn(false); + Mockito.when(request.getParameterNames()).thenReturn(Collections.emptyEnumeration()); + } + + @Test public void unsupported() { + UriInfo info = new UriInfoImpl(request, new PathValues()); + assertThrows(UnsupportedOperationException.class, () -> info.getPathSegments()); + assertThrows(UnsupportedOperationException.class, () -> info.getPathSegments(false)); + assertThrows(UnsupportedOperationException.class, () -> info.getMatchedResources()); + assertThrows(UnsupportedOperationException.class, () -> info.getMatchedURIs()); + assertThrows(UnsupportedOperationException.class, () -> info.getMatchedURIs(false)); + } + + @Test public void baseUriXForwardedProto() { + Mockito.when(request.getHeader("X-Forwarded-Proto")).thenReturn("https"); + UriInfo info = new UriInfoImpl(request, new PathValues()); + assertEquals("https", info.getBaseUri().getScheme()); + } + + @Test public void baseUriIsSecure() { + Mockito.when(request.isSecure()).thenReturn(true); + UriInfo info = new UriInfoImpl(request, new PathValues()); + assertEquals("https", info.getBaseUri().getScheme()); + } + + @Test public void baseUriXForwardedHost() { + Mockito.when(request.getHeader("X-Forwarded-Host")).thenReturn("google.com"); + UriInfo info = new UriInfoImpl(request, new PathValues()); + assertEquals("google.com", info.getBaseUri().getHost()); + } + + @Test public void baseUriWrongXForwardedHost() throws URISyntaxException { + Mockito.when(request.getHeader("X-Forwarded-Host")).thenReturn("aaa .com"); + assertThrows(IllegalStateException.class, () -> new UriInfoImpl(request, new PathValues())); + } + + @Test public void baseUriLocal() { + Mockito.when(request.getHeader("Host")).thenReturn(null); + Mockito.when(request.getLocalAddr()).thenReturn("2607:f0d0:1002:0051:0000:0000:0000:0004"); + Mockito.when(request.getLocalPort()).thenReturn(8080); + UriInfo info = new UriInfoImpl(request, new PathValues()); + assertEquals("[2607:f0d0:1002:0051:0000:0000:0000:0004]:8080", info.getBaseUri().getAuthority()); + } + + @Test public void baseContextPath() { + Mockito.when(request.getContextPath()).thenReturn("/"); + assertEquals("http://cactusthorn.net/", (new UriInfoImpl(request, new PathValues())).getBaseUri().toString()); + + Mockito.when(request.getContextPath()).thenReturn("app/"); + assertEquals("http://cactusthorn.net/app/", new UriInfoImpl(request, new PathValues()).getBaseUri().toString()); + + Mockito.when(request.getContextPath()).thenReturn("/app"); + assertEquals("http://cactusthorn.net/app/", new UriInfoImpl(request, new PathValues()).getBaseUri().toString()); + + Mockito.when(request.getContextPath()).thenReturn("/app/"); + assertEquals("http://cactusthorn.net/app/", new UriInfoImpl(request, new PathValues()).getBaseUri().toString()); + } + + @Test public void getBaseUriBuilder() { + assertEquals("http://cactusthorn.net/", new UriInfoImpl(request, new PathValues()).getBaseUriBuilder().build().toString()); + } + + @Test public void path() { + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + assertEquals("app/xxx", new UriInfoImpl(request, new PathValues()).getPath()); + + Mockito.when(request.getPathInfo()).thenReturn(""); + assertEquals("", new UriInfoImpl(request, new PathValues()).getPath()); + + Mockito.when(request.getPathInfo()).thenReturn(null); + assertEquals("", new UriInfoImpl(request, new PathValues()).getPath()); + + Mockito.when(request.getPathInfo()).thenReturn("/"); + assertEquals("", new UriInfoImpl(request, new PathValues()).getPath()); + } + + @Test public void pathEncoded() throws URISyntaxException { + Mockito.when(request.getPathInfo()).thenReturn("app/xüxx"); + assertEquals("app/x%C3%BCxx", new UriInfoImpl(request, new PathValues()).getPath(false)); + } + + @Test public void getAbsolutePath() { + Mockito.when(request.getContextPath()).thenReturn("cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt"); + Mockito.when(request.getPathInfo()).thenReturn("/app/xxx"); + assertEquals("http://cactusthorn.net/cntxt/srvlt/app/xxx", new UriInfoImpl(request, new PathValues()).getAbsolutePath().toString()); + } + + @Test public void getAbsolutePathBuilder() { + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + assertEquals("http://cactusthorn.net/app/xxx", + new UriInfoImpl(request, new PathValues()).getAbsolutePathBuilder().build().toString()); + } + + @Test public void getRequestUri() { + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + Mockito.when(request.getQueryString()).thenReturn("aaa=bb cc"); + assertEquals("http://cactusthorn.net/app/xxx?aaa=bb%20cc", new UriInfoImpl(request, new PathValues()).getRequestUri().toString()); + } + + @Test public void getRequestNullQuery() { + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + assertEquals("http://cactusthorn.net/app/xxx", new UriInfoImpl(request, new PathValues()).getRequestUri().toString()); + } + + @Test public void getRequestUriBuilder() { + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + Mockito.when(request.getQueryString()).thenReturn("aaa=bb cc"); + assertEquals("http://cactusthorn.net/app/xxx?aaa=bb%20cc", + new UriInfoImpl(request, new PathValues()).getRequestUriBuilder().build().toString()); + } + + @Test public void resolve() { + Mockito.when(request.getContextPath()).thenReturn("/cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt/"); + Mockito.when(request.getPathInfo()).thenReturn("app/xxx"); + Mockito.when(request.getQueryString()).thenReturn("aaa=bb cc"); + assertEquals("http://cactusthorn.net/cntxt/srvlt/ssss/mmm?aa=bb", + new UriInfoImpl(request, new PathValues()).resolve(URI.create("ssss/mmm?aa=bb")).toString()); + } + + @Test public void relativizeA() { + Mockito.when(request.getContextPath()).thenReturn("/cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt/"); + Mockito.when(request.getPathInfo()).thenReturn("/a/b/c/resource.html"); + assertEquals("d/file.txt", new UriInfoImpl(request, new PathValues()).relativize(URI.create("a/b/c/d/file.txt")).toString()); + } + + @Test public void relativizeB() { + Mockito.when(request.getContextPath()).thenReturn("/cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt/"); + Mockito.when(request.getPathInfo()).thenReturn("/a/b/c/resource.html"); + assertEquals("http://example2.com:9090/app2/root2/a/d/file.txt", new UriInfoImpl(request, new PathValues()) + .relativize(URI.create("http://example2.com:9090/app2/root2/a/d/file.txt")).toString()); + } + + @Test public void relativizeC() { + Mockito.when(request.getPathInfo()).thenReturn(""); + assertEquals("a/d/file.txt", new UriInfoImpl(request, new PathValues()).relativize(URI.create("/a/d/file.txt")).toString()); + } + + @Test public void relativizeD() { + Mockito.when(request.getContextPath()).thenReturn("/cntxt"); + Mockito.when(request.getServletPath()).thenReturn("/srvlt/"); + Mockito.when(request.getPathInfo()).thenReturn("/a/b/c/resource.html"); + assertEquals("file.txt", new UriInfoImpl(request, new PathValues()).relativize(URI.create("a/b/file.txt")).toString()); + } + + @Test public void relativizeE() throws URISyntaxException { + Mockito.when(request.getPathInfo()).thenReturn(""); + URI to = new URI(null, null, null, "aa=bb", null); + assertEquals("?aa=bb", new UriInfoImpl(request, new PathValues()).relativize(to).toString()); + } + + @Test public void relativizeOpaque() { + Mockito.when(request.getPathInfo()).thenReturn(""); + assertEquals("mailto:a@a.com", new UriInfoImpl(request, new PathValues()).relativize(URI.create("mailto:a@a.com")).toString()); + } + + @Test public void relativizeSchema() { + Mockito.when(request.getPathInfo()).thenReturn(""); + assertEquals("https://cactusthorn.net/a?a=b", + new UriInfoImpl(request, new PathValues()).relativize(URI.create("https://cactusthorn.net/a?a=b")).toString()); + } + + @Test public void relativizeNoPath() { + Mockito.when(request.getPathInfo()).thenReturn(""); + assertEquals("", + new UriInfoImpl(request, new PathValues()).relativize(URI.create("http://cactusthorn.net")).toString()); + } + + @Test public void getPathParameters() { + Mockito.when(request.getPathInfo()).thenReturn(""); + PathValues values = new PathValues(); + values.put("aa", "bb cc"); + values.put("bb", "xxxx"); + MultivaluedMap map = new UriInfoImpl(request, values).getPathParameters(); + assertEquals("bb cc", map.getFirst("aa")); + assertEquals("xxxx", map.getFirst("bb")); + assertThrows(UnsupportedOperationException.class, () -> map.addFirst("xx", "yy")); + } + + @Test public void getPathParametersEncode() { + Mockito.when(request.getPathInfo()).thenReturn(""); + PathValues values = new PathValues(); + values.put("aa", "bb cc"); + values.put("bb", "xxxx"); + MultivaluedMap map = new UriInfoImpl(request, values).getPathParameters(false); + assertEquals("bb%20cc", map.getFirst("aa")); + assertEquals("xxxx", map.getFirst("bb")); + } + + @Test public void getQueryParameters() { + + Map parameters = new HashMap<>(); + parameters.put("aaa", new String[] { "aa bb", "ccdd" }); + + Mockito.when(request.getParameterNames()).thenReturn(Collections.enumeration(parameters.keySet())); + Mockito.when(request.getParameterValues("aaa")).thenReturn(parameters.get("aaa")); + + MultivaluedMap map = new UriInfoImpl(request, new PathValues()).getQueryParameters(); + assertEquals("aa bb", map.get("aaa").get(0)); + assertEquals("ccdd", map.get("aaa").get(1)); + assertThrows(UnsupportedOperationException.class, () -> map.addFirst("xx", "yy")); + } + + @Test public void getQueryParametersEncode() { + + Map parameters = new HashMap<>(); + parameters.put("aaa", new String[] { "aa bb", "ccdd" }); + + Mockito.when(request.getParameterNames()).thenReturn(Collections.enumeration(parameters.keySet())); + Mockito.when(request.getParameterValues("aaa")).thenReturn(parameters.get("aaa")); + + MultivaluedMap map = new UriInfoImpl(request, new PathValues()).getQueryParameters(false); + assertEquals("aa%20bb", map.get("aaa").get(0)); + } +} diff --git a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/resource/SimpleResource.java b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/resource/SimpleResource.java index d9cb588..cc274a4 100644 --- a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/resource/SimpleResource.java +++ b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/resource/SimpleResource.java @@ -19,6 +19,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; import net.cactusthorn.routing.annotation.Template; import net.cactusthorn.routing.demo.jetty.dagger.Resource; @@ -80,4 +81,9 @@ public String headers(@Context HttpHeaders headers) { public String exceptionmapper() { throw new UnsupportedOperationException(); } + + @GET @Path("/uri") // + public String uri(@Context UriInfo uriInfo) { + return uriInfo.getRequestUri().toString(); + } } diff --git a/demo-jetty/src/main/resources/thymeleaf/index.html b/demo-jetty/src/main/resources/thymeleaf/index.html index 47584f0..89c9124 100644 --- a/demo-jetty/src/main/resources/thymeleaf/index.html +++ b/demo-jetty/src/main/resources/thymeleaf/index.html @@ -44,6 +44,8 @@ HttpHeaders

ExceptionMapper (HTTP 503)

+ + UriInfo

\ No newline at end of file