Skip to content

Commit

Permalink
#27 : Context-annotation should support javax.ws.rs.core.UriInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
Gmugra committed Mar 6, 2021
1 parent cfe2cdb commit 4b9007b
Show file tree
Hide file tree
Showing 10 changed files with 648 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
15 changes: 15 additions & 0 deletions core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -31,6 +36,16 @@ public void put(String name, String value) {
public String value(String name) {
return values.get(name);
}

public MultivaluedMap<String, String> toMultivaluedMap(boolean decode) {
MultivaluedMap<String, String> 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<PathTemplate> COMPARATOR = (o1, o2) -> {
Expand Down
277 changes: 277 additions & 0 deletions core/src/main/java/net/cactusthorn/routing/uri/UriInfoImpl.java
Original file line number Diff line number Diff line change
@@ -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<String, String> decodedQueryParameters;
private MultivaluedMap<String, String> 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: <host>
if (xForwardedHost != null) {
return new URI(findScheme(request) + SCHEME_SEPARATOR + xForwardedHost + basePath);
}
String hostHeader = request.getHeader(HttpHeaders.HOST); // Host: <host>:<port>
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<PathSegment> getPathSegments() {
throw new UnsupportedOperationException();
}

@Override public List<PathSegment> 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<String, String> getPathParameters() {
return getPathParameters(true);
}

@Override public MultivaluedMap<String, String> getPathParameters(boolean decode) {
return pathValues.toMultivaluedMap(decode);
}

@Override public MultivaluedMap<String, String> getQueryParameters() {
return getQueryParameters(true);
}

@Override public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
if (decode) {
return decodedQueryParameters;
}
return encodedQueryParameters;
}

@Override public List<String> getMatchedURIs() {
throw new UnsupportedOperationException();
}

@Override public List<String> getMatchedURIs(boolean decode) {
throw new UnsupportedOperationException();
}

@Override public List<Object> 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("/");
}

}
Loading

0 comments on commit 4b9007b

Please sign in to comment.