diff --git a/core/src/main/java/net/cactusthorn/routing/PathTemplate.java b/core/src/main/java/net/cactusthorn/routing/PathTemplate.java deleted file mode 100644 index c02373e..0000000 --- a/core/src/main/java/net/cactusthorn/routing/PathTemplate.java +++ /dev/null @@ -1,277 +0,0 @@ -package net.cactusthorn.routing; - -import static java.text.CharacterIterator.DONE; - -import java.net.URI; -import java.net.URISyntaxException; -import java.text.StringCharacterIterator; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class PathTemplate { - - public static final class PathValues { - - public static final PathValues EMPTY = new PathValues(); - - private final Map values = new HashMap<>(); - - public PathValues() { - } - - public PathValues(String name, String value) { - values.put(name, value); - } - - public void put(String name, String value) { - values.put(name, value); - } - - public String value(String name) { - return values.get(name); - } - } - - public static final Comparator COMPARATOR = (o1, o2) -> { - if (o1 == null && o2 == null) { - return 0; - } - if (o1 == null) { - return 1; - } - if (o2 == null) { - return -1; - } - - int i = o2.literalCharsAmount() - o1.literalCharsAmount(); - if (i != 0) { - return i; - } - - i = o2.parametersAmount() - o1.parametersAmount(); - if (i != 0) { - return i; - } - - i = o2.regExpParametersAmount() - o1.regExpParametersAmount(); - if (i != 0) { - return i; - } - - return 0; - }; - - private String literal; - private int literalCharsAmount; - - private Pattern pattern; - - private final ArrayList parameters = new ArrayList<>(); - - private boolean simple; - private int simpleParamsAmount; - private int regExpParamsAmount; - - public PathTemplate(String template) { - if (template == null) { - throw new IllegalArgumentException("Template is null"); - } - String prepared = template.trim(); - if (prepared.isEmpty()) { - throw new IllegalArgumentException("Template is empty"); - } - process(prepared); - } - - public static final String SIMPLE_PATTERN = "[^/]+"; - - private void process(String prepared) { - - StringBuilder literalBuf = new StringBuilder(); - StringBuilder patternBuf = new StringBuilder(); - Map paramsBuf = new HashMap<>(); - - StringCharacterIterator it = new StringCharacterIterator(prepared); - for (char c = it.first(); c != DONE; c = it.next()) { - if (c == '{') { - String paramPattern = processParam(it, paramsBuf); - patternBuf.append('(').append(paramPattern).append(')'); - } else { - patternBuf.append(c); - literalBuf.append(c); - } - } - - literal = literalBuf.toString(); - literalCharsAmount = literalBuf.length(); - - simple = parameters.isEmpty(); - - pattern = Pattern.compile(patternBuf.toString()); - - // It must be valid URI after cut all parameters - // TODO not the best way: check only valid for URI characters before - try { - new URI(literal); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Template is invalid URI", e); - } - } - - private static final String WRONG_PARAM = "Template contain improperly closed parameter"; - private static final String WRONG_CHAR = "Template contain wrong character for parameter name at the possition %d"; - private static final String MULTIPLE_PARAM = "Template contain parameter \"%s\" multiple times with different pattern"; - - private String processParam(StringCharacterIterator it, Map paramsBuf) { - - StringBuilder param = new StringBuilder(); - int lastSpaceIndex = 0; - - char c = eraseWhitespaces(it); - do { - if (c == '{') { - throw new IllegalArgumentException(WRONG_PARAM); - } - if (c == '}') { - String paramName = param.toString(); - paramsBuf.put(paramName, SIMPLE_PATTERN); - parameters.add(param.toString()); - simpleParamsAmount++; - return SIMPLE_PATTERN; - } - if (c == ':') { - String regExp = processRegExp(it); - String paramName = param.toString(); - if (paramsBuf.containsKey(paramName) && !regExp.equals(paramsBuf.get(paramName))) { - throw new IllegalArgumentException(String.format(MULTIPLE_PARAM, paramName)); - } - parameters.add(param.toString()); - regExpParamsAmount++; - return regExp; - } - if (Character.isLetterOrDigit(c) || c == '_') { - if (lastSpaceIndex == 0) { - param.append(c); - } else { - throw new IllegalArgumentException(String.format(WRONG_CHAR, lastSpaceIndex + 1)); - } - } else if (Character.isWhitespace(c)) { - lastSpaceIndex = it.getIndex(); - } else { - throw new IllegalArgumentException(String.format(WRONG_CHAR, it.getIndex() + 1)); - } - c = it.next(); - } while (true); - } - - private char eraseWhitespaces(StringCharacterIterator it) { - while (true) { - char c = it.next(); - if (!Character.isWhitespace(c)) { - return c; - } - } - } - - private String processRegExp(StringCharacterIterator it) { - - int opened = 1; - StringBuilder regExp = new StringBuilder(); - - char c = eraseWhitespaces(it); - do { - if (c == '{') { - opened++; - } else if (c == '}') { - opened--; - if (opened == 0) { - return regExp.toString().trim(); - } - } - regExp.append(c); - c = it.next(); - } while (c != DONE); - - throw new IllegalArgumentException(WRONG_PARAM); - } - - public boolean match(String path) { - if (simple) { - return literal.equals(path); - } - Matcher matcher = pattern.matcher(path); - return matcher.find(); - } - - public PathValues parse(String path) { - if (simple) { - if (literal.equals(path)) { - return PathValues.EMPTY; - } - return null; - } - Matcher matcher = pattern.matcher(path); - if (matcher.find()) { - PathValues pathValues = new PathValues(); - for (int i = 0; i < parameters.size(); i++) { - pathValues.put(parameters.get(i), matcher.group(i + 1)); - } - return pathValues; - } - return null; - } - - public Pattern pattern() { - return pattern; - } - - public List parameters() { - return parameters; - } - - public int literalCharsAmount() { - return literalCharsAmount; - } - - public boolean isSimple() { - return simple; - } - - public int simpleParametersAmount() { - return simpleParamsAmount; - } - - public int regExpParametersAmount() { - return regExpParamsAmount; - } - - public int parametersAmount() { - return simpleParamsAmount + regExpParamsAmount; - } - - @Override // - public int hashCode() { - return pattern.pattern().hashCode(); - } - - @Override // - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof PathTemplate)) { - return false; - } - PathTemplate template = (PathTemplate) obj; - if (!pattern.pattern().equals(template.pattern().pattern())) { - return false; - } - return parameters.equals(template.parameters); - } -} diff --git a/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java b/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java index 81c68b1..1039d42 100644 --- a/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java +++ b/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java @@ -25,9 +25,9 @@ import net.cactusthorn.routing.invoke.MethodInvoker.ReturnObjectInfo; import net.cactusthorn.routing.resource.ResourceScanner; import net.cactusthorn.routing.resource.ResourceScanner.Resource; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.util.Headers; import net.cactusthorn.routing.util.Http; -import net.cactusthorn.routing.PathTemplate.PathValues; public class RoutingServlet extends HttpServlet { diff --git a/core/src/main/java/net/cactusthorn/routing/delegate/LinkImpl.java b/core/src/main/java/net/cactusthorn/routing/delegate/LinkImpl.java index d432fdd..14741c9 100644 --- a/core/src/main/java/net/cactusthorn/routing/delegate/LinkImpl.java +++ b/core/src/main/java/net/cactusthorn/routing/delegate/LinkImpl.java @@ -27,7 +27,7 @@ private LinkImpl(URI uri, Map params) { } @Override public UriBuilder getUriBuilder() { - throw new UnsupportedOperationException(); + return UriBuilder.fromUri(uri); } @Override public String getRel() { @@ -65,11 +65,12 @@ private LinkImpl(URI uri, Map params) { public static class LinkBuilderImpl implements Builder { - private URI uuri; + private UriBuilder uriBuilder; + private URI baseUri; private final Map params = new LinkedHashMap<>(); @Override public Builder link(Link link) { - this.uuri = link.getUri(); + uriBuilder = UriBuilder.fromUri(link.getUri()); params.clear(); params.putAll(link.getParams()); return this; @@ -86,7 +87,8 @@ public static class LinkBuilderImpl implements Builder { if (tmp.charAt(uriAsStr.length() - 1) != '>') { throw new IllegalArgumentException("Wrong: '>' is missing"); } - uuri = URI.create(uriAsStr.substring(1, uriAsStr.length() - 1).trim()); + String uri = uriAsStr.substring(1, uriAsStr.length() - 1).trim(); + uriBuilder = UriBuilder.fromUri(URI.create(uri)); params.clear(); for (int i = 1; i < parts.length; i++) { @@ -98,53 +100,80 @@ public static class LinkBuilderImpl implements Builder { } @Override public Builder uri(URI uri) { - this.uuri = uri; + uriBuilder = UriBuilder.fromUri(uri); return this; } @Override public Builder uri(String uri) { - this.uuri = URI.create(uri); + uriBuilder = UriBuilder.fromUri(uri); return this; } @Override public Builder baseUri(URI uri) { - throw new UnsupportedOperationException(); + this.baseUri = uri; + return this; } @Override public Builder baseUri(String uri) { - throw new UnsupportedOperationException(); + this.baseUri = URI.create(uri); + return this; } - @Override public Builder uriBuilder(UriBuilder uriBuilder) { - throw new UnsupportedOperationException(); + @Override public Builder uriBuilder(UriBuilder builder) { + this.uriBuilder = builder.clone(); + return this; } @Override public Builder rel(String rel) { + if (rel == null) { + throw new IllegalArgumentException("rel is null"); + } params.put(REL, rel); return this; } @Override public Builder title(String title) { + if (title == null) { + throw new IllegalArgumentException("title is null"); + } params.put(TITLE, title); return this; } @Override public Builder type(String type) { + if (type == null) { + throw new IllegalArgumentException("type is null"); + } params.put(TYPE, type); return this; } @Override public Builder param(String name, String value) { + if (name == null) { + throw new IllegalArgumentException("name is null"); + } + if (value == null) { + throw new IllegalArgumentException("value is null"); + } params.put(name, value); return this; } @Override public Link build(Object... values) { - return new LinkImpl(uuri, params); + URI linkUri = uriBuilder.build(values); + return new LinkImpl(linkUri, params); } - @Override public Link buildRelativized(URI uri, Object... vvalues) { - throw new UnsupportedOperationException(); + @Override public Link buildRelativized(URI uri, Object... values) { + if (uri == null) { + throw new IllegalArgumentException("uri is null"); + } + URI result = uriBuilder.build(values); + URI with = result; + if (baseUri != null) { + with = baseUri.resolve(result); + } + return new LinkImpl(uri.relativize(with), params); } } } diff --git a/core/src/main/java/net/cactusthorn/routing/delegate/RuntimeDelegateImpl.java b/core/src/main/java/net/cactusthorn/routing/delegate/RuntimeDelegateImpl.java index 58577f5..870e702 100644 --- a/core/src/main/java/net/cactusthorn/routing/delegate/RuntimeDelegateImpl.java +++ b/core/src/main/java/net/cactusthorn/routing/delegate/RuntimeDelegateImpl.java @@ -39,7 +39,7 @@ public RuntimeDelegateImpl() { @Override // public UriBuilder createUriBuilder() { - throw new UnsupportedOperationException("createUriBuilder() is not supported"); + return new UriBuilderImpl(); } @Override // diff --git a/core/src/main/java/net/cactusthorn/routing/delegate/UriBuilderImpl.java b/core/src/main/java/net/cactusthorn/routing/delegate/UriBuilderImpl.java new file mode 100644 index 0000000..b5eddc0 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/delegate/UriBuilderImpl.java @@ -0,0 +1,642 @@ +package net.cactusthorn.routing.delegate; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +import javax.ws.rs.Path; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; + +import net.cactusthorn.routing.uri.Template; +import net.cactusthorn.routing.uri.UriComponentEncoder; +import net.cactusthorn.routing.uri.UriTemplate; + +public class UriBuilderImpl extends UriBuilder implements Cloneable { + + private boolean opaque; + private String scheme; + private String schemeSpecificPart; + + private String authority; + private String userInfo; + private String host; + private String port; + + private String path; + private String query; + + private String fragment; + + @Override public UriBuilder clone() { + UriBuilderImpl builder = new UriBuilderImpl(); + builder.opaque = opaque; + builder.scheme = scheme; + builder.schemeSpecificPart = schemeSpecificPart; + builder.authority = authority; + builder.userInfo = userInfo; + builder.host = host; + builder.port = port; + builder.path = path; + builder.query = query; + builder.fragment = fragment; + return builder; + } + + /** + * Copies the non-null components of the supplied URI to the UriBuilder + * replacing any existing values for those components. + */ + @Override public UriBuilder uri(URI uri) { + if (uri == null) { + throw new IllegalArgumentException("uri is null"); + } + if (uri.getScheme() != null) { + scheme = uri.getScheme(); + } + if (uri.getRawFragment() != null) { + fragment = uri.getRawFragment(); + } + if (uri.isOpaque()) { + opaque = true; + schemeSpecificPart = uri.getRawSchemeSpecificPart(); + authority = null; + userInfo = null; + host = null; + port = null; + path = null; + query = null; + } else { + opaque = false; + schemeSpecificPart = null; + if (uri.getRawAuthority() != null) { + if (uri.getUserInfo() == null && uri.getHost() == null && uri.getPort() == -1) { + authority = uri.getRawAuthority(); + userInfo = null; + host = null; + port = null; + } else { + authority = null; + if (uri.getRawUserInfo() != null) { + userInfo = uri.getRawUserInfo(); + } + if (uri.getHost() != null) { + host = uri.getHost(); + } + if (uri.getPort() != -1) { + port = String.valueOf(uri.getPort()); + } + } + } + if (!uri.getRawPath().isEmpty()) { + path = uri.getRawPath(); + } + if (uri.getRawQuery() != null && !uri.getRawQuery().isEmpty()) { + query = uri.getRawQuery(); + } + } + return this; + } + + /** + * Parses the {@code uriTemplate} string and copies the parsed components of the + * supplied URI to the UriBuilder replacing any existing values for those + * components. + */ + @Override public UriBuilder uri(String uriTemplate) { + if (uriTemplate == null) { + throw new IllegalArgumentException("uriTemplate is null"); + } + UriTemplate template = new UriTemplate(uriTemplate); + opaque = template.opaque(); + scheme = template.scheme(); + schemeSpecificPart = template.schemeSpecificPart(); + authority = template.authority(); + userInfo = template.userInfo(); + host = template.host(); + port = template.port(); + path = template.path(); + query = template.query(); + fragment = template.fragment(); + return this; + } + + @Override public UriBuilder scheme(String s) { + if (s == null) { + scheme = null; + } else { + scheme = new Template(s).template(); + } + return this; + } + + /** + * Set the URI scheme-specific-part (see {@link java.net.URI}). This method will + * overwrite any existing values for authority, user-info, host, port and path. + */ + @Override public UriBuilder schemeSpecificPart(String ssp) { + if (ssp == null) { + throw new IllegalArgumentException("ssp is null"); + } + UriTemplate template = new UriTemplate(scheme != null ? scheme + ':' + ssp : ssp); + if (template.fragment() != null) { + throw new IllegalArgumentException("schemeSpecificPart must not contain fragment"); + } + opaque = template.opaque(); + if (opaque) { + schemeSpecificPart = template.schemeSpecificPart(); + authority = null; + userInfo = null; + host = null; + port = null; + path = null; + query = null; + } else { + schemeSpecificPart = null; + authority = template.authority(); + userInfo = template.userInfo(); + host = template.host(); + port = template.port(); + path = template.path(); + query = template.query(); + } + return this; + } + + @Override public UriBuilder userInfo(String ui) { + if (opaque) { + throw new IllegalArgumentException("Can't set userInfo for opaque URI"); + } + if (ui == null) { + userInfo = null; + } else { + userInfo = new Template(UriComponentEncoder.USER_INFO.encode(ui)).template(); + } + if (ui != null) { + authority = null; + } + return this; + } + + @Override public UriBuilder host(String h) { + if (opaque) { + throw new IllegalArgumentException("Can't set host for opaque URI"); + } + if (h == null) { + host = null; + } else { + host = new Template(h).template(); + } + if (h != null) { + authority = null; + } + return this; + } + + @Override public UriBuilder port(int p) { + if (opaque) { + throw new IllegalArgumentException("Can't set port for opaque URI"); + } + if (p < -1) { + throw new IllegalArgumentException("Invalid port"); + } + if (p != -1) { + authority = null; + port = String.valueOf(p); + } else { + port = null; + } + return this; + } + + @Override public UriBuilder replacePath(String p) { + if (opaque) { + throw new IllegalArgumentException("Can't replace Path for opaque URI"); + } + if (p == null) { + path = null; + } else { + path = new Template(UriComponentEncoder.PATH.encode(p)).template(); + } + return this; + } + + @Override public UriBuilder path(String p) { + if (opaque) { + throw new IllegalArgumentException("Can't append Path for opaque URI"); + } + if (p == null) { + throw new IllegalArgumentException("path is null"); + } + return appendPath(UriComponentEncoder.PATH.encode(p)); + } + + @Override public UriBuilder path(@SuppressWarnings("rawtypes") Class resource) { + if (opaque) { + throw new IllegalArgumentException("Can't append Path for opaque URI"); + } + if (resource == null) { + throw new IllegalArgumentException("resource is null"); + } + @SuppressWarnings("unchecked") Path annotation = (Path) resource.getAnnotation(Path.class); + if (annotation == null) { + throw new IllegalArgumentException("resource do not has @Path annotation"); + } + return appendPath(UriComponentEncoder.PATH.encode(annotation.value())); + } + + @Override public UriBuilder path(@SuppressWarnings("rawtypes") Class resource, String method) { + if (opaque) { + throw new IllegalArgumentException("Can't append Path for opaque URI"); + } + if (resource == null) { + throw new IllegalArgumentException("resource is null"); + } + if (method == null) { + throw new IllegalArgumentException("method is null"); + } + String pathValue = null; + for (Method m : resource.getMethods()) { + if (method.equals(m.getName())) { + Path annotation = (Path) m.getAnnotation(Path.class); + if (annotation != null) { + if (pathValue != null) { + throw new IllegalArgumentException("there is more than one variant of the method"); + } + pathValue = annotation.value(); + } + } + } + if (pathValue == null) { + throw new IllegalArgumentException("method not exist"); + } + return appendPath(UriComponentEncoder.PATH.encode(pathValue)); + } + + @Override public UriBuilder path(Method method) { + if (opaque) { + throw new IllegalArgumentException("Can't append Path for opaque URI"); + } + if (method == null) { + throw new IllegalArgumentException("method is null"); + } + Path annotation = (Path) method.getAnnotation(Path.class); + if (annotation == null) { + throw new IllegalArgumentException("method do not has @Path annotation"); + } + return appendPath(UriComponentEncoder.PATH.encode(annotation.value())); + } + + @Override public UriBuilder segment(String... segments) { + if (opaque) { + throw new IllegalArgumentException("Can't append Path-segments for opaque URI"); + } + if (segments == null) { + throw new IllegalArgumentException("segments are null"); + } + for (String segment : segments) { + if (segment == null) { + throw new IllegalArgumentException("segment is null"); + } + appendPath(UriComponentEncoder.PATH_SEGMENT.encode(segment)); + } + return this; + } + + @Override public UriBuilder replaceMatrix(String matrix) { + throw new UnsupportedOperationException(); + } + + @Override public UriBuilder matrixParam(String name, Object... values) { + throw new UnsupportedOperationException(); + } + + @Override public UriBuilder replaceMatrixParam(String name, Object... values) { + throw new UnsupportedOperationException(); + } + + @Override public UriBuilder replaceQuery(String q) { + if (opaque) { + throw new IllegalArgumentException("Can't replace Query for opaque URI"); + } + if (q == null) { + query = null; + } else { + query = new Template(UriComponentEncoder.QUERY.encode(q)).template(); + } + return this; + } + + @Override public UriBuilder queryParam(String name, Object... values) { + if (opaque) { + throw new IllegalArgumentException("Can't append Query param for opaque URI"); + } + if (name == null) { + throw new IllegalArgumentException("name is null"); + } + if (values == null) { + throw new IllegalArgumentException("values is null"); + } + if (values.length == 0) { + return this; + } + String encodedName = new Template(UriComponentEncoder.QUERY_PARAM.encode(name)).template(); + concatQueryParam(encodedName, values); + return this; + } + + @Override public UriBuilder replaceQueryParam(String name, Object... values) { + if (opaque) { + throw new IllegalArgumentException("Can't replace Query param for opaque URI"); + } + if (name == null) { + throw new IllegalArgumentException("name is null"); + } + String encodedName = new Template(UriComponentEncoder.QUERY_PARAM.encode(name)).template(); + removeQueryParam(encodedName); + if (values == null || values.length == 0) { + return this; + } + concatQueryParam(encodedName, values); + return this; + } + + @Override public UriBuilder fragment(String f) { + if (f == null) { + fragment = null; + } else { + fragment = new Template(UriComponentEncoder.FRAGMENT.encode(f)).template(); + } + return this; + } + + @Override public UriBuilder resolveTemplate(String name, Object value) { + return resolve(name, value, false, false); + } + + @Override public UriBuilder resolveTemplate(String name, Object value, boolean encodeSlashInPath) { + return resolve(name, value, encodeSlashInPath, false); + } + + @Override public UriBuilder resolveTemplateFromEncoded(String name, Object value) { + return resolve(name, value, false, true); + } + + @Override public UriBuilder resolveTemplates(Map templateValues) { + if (templateValues == null) { + throw new IllegalArgumentException("templateValues is null"); + } + return resolve(templateValues, false, false); + } + + @Override public UriBuilder resolveTemplates(Map templateValues, boolean encodeSlashInPath) + throws IllegalArgumentException { + if (templateValues == null) { + throw new IllegalArgumentException("templateValues is null"); + } + return resolve(templateValues, encodeSlashInPath, false); + } + + @Override public UriBuilder resolveTemplatesFromEncoded(Map templateValues) { + if (templateValues == null) { + throw new IllegalArgumentException("templateValues is null"); + } + return resolve(templateValues, false, true); + } + + @Override @SuppressWarnings("unchecked") public URI buildFromMap(Map values) { + if (values == null) { + throw new IllegalArgumentException("values is null"); + } + try { + UriBuilder clonned = this.clone(); + clonned.resolveTemplates((Map) values); + return clonned.build(); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override @SuppressWarnings("unchecked") public URI buildFromMap(Map values, boolean encodeSlashInPath) + throws IllegalArgumentException, UriBuilderException { + if (values == null) { + throw new IllegalArgumentException("values is null"); + } + try { + UriBuilder clonned = this.clone(); + clonned.resolveTemplates((Map) values, encodeSlashInPath); + return clonned.build(); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override @SuppressWarnings("unchecked") public URI buildFromEncodedMap(Map values) + throws IllegalArgumentException, UriBuilderException { + if (values == null) { + throw new IllegalArgumentException("values is null"); + } + try { + UriBuilder clonned = this.clone(); + clonned.resolveTemplatesFromEncoded((Map) values); + return clonned.build(); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override public URI build(Object... values) throws IllegalArgumentException, UriBuilderException { + if (values != null && values.length != 0) { + Map variables = buildMap(values); + return buildFromMap(variables); + } + try { + return new URI(toTemplate()); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override public URI build(Object[] values, boolean encodeSlashInPath) throws IllegalArgumentException, UriBuilderException { + if (values != null && values.length != 0) { + Map variables = buildMap(values); + return buildFromMap(variables, encodeSlashInPath); + } + try { + return new URI(toTemplate()); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override public URI buildFromEncoded(Object... values) throws IllegalArgumentException, UriBuilderException { + if (values != null && values.length != 0) { + Map variables = buildMap(values); + return buildFromEncodedMap(variables); + } + try { + return new URI(toTemplate()); + } catch (Exception e) { + throw new UriBuilderException(e); + } + } + + @Override public String toTemplate() { + StringBuilder buf = new StringBuilder(); + if (scheme != null) { + buf.append(scheme).append(':'); + } + if (opaque) { + buf.append(schemeSpecificPart); + } else { + if (authority != null) { + buf.append("//").append(authority); + } else if (userInfo != null || host != null || port != null) { + buf.append("//"); + if (userInfo != null) { + buf.append(userInfo).append('@'); + } + if (host != null) { + buf.append(host); + } + if (port != null) { + buf.append(':').append(port); + } + } + if (path != null) { + if (buf.length() != 0 && !path.isEmpty() && !path.startsWith("/")) { + buf.append("/"); + } + buf.append(path); + } + if (query != null) { + buf.append('?').append(query); + } + } + if (fragment != null) { + buf.append('#').append(fragment); + } + return buf.toString(); + } + + private Map buildMap(Object[] values) { + Template template = new Template(toTemplate()); + Map variables = new HashMap<>(); + int position = -1; + for (Template.TemplateVariable var : template.variables()) { + if (variables.containsKey(var.name())) { + continue; + } + position++; + if (position >= values.length) { + throw new IllegalArgumentException("Not enough values for template variables"); + } + if (values[position] == null) { + throw new IllegalArgumentException("Null value is not allowed"); + } + variables.put(var.name(), values[position]); + } + return variables; + } + + private UriBuilder resolve(Map templateValues, boolean encodeSlashInPath, boolean fromEncoded) { + for (Map.Entry entry : templateValues.entrySet()) { + resolve(entry.getKey(), entry.getValue(), encodeSlashInPath, fromEncoded); + } + return this; + } + + private UriBuilder resolve(String name, Object value, boolean encodeSlashInPath, boolean fromEncoded) { + if (name == null) { + throw new IllegalArgumentException("template variable name can't be null"); + } + if (value == null) { + throw new IllegalArgumentException("template variable value can't be null"); + } + String variable = '{' + name + '}'; + if (scheme != null) { + scheme = scheme.replace(variable, value.toString()); + } + if (opaque) { + schemeSpecificPart = schemeSpecificPart.replace(variable, + UriComponentEncoder.SCHEME_SPECIFIC_PART.encode(value.toString(), fromEncoded)); + } else { + if (authority != null) { + authority = authority.replace(variable, value.toString()); + } else { + if (userInfo != null) { + userInfo = userInfo.replace(variable, UriComponentEncoder.USER_INFO.encode(value.toString(), fromEncoded)); + } + if (host != null) { + host = host.replace(variable, value.toString()); + } + if (port != null) { + port = port.replace(variable, value.toString()); + } + } + if (path != null) { + String prepared; + if (encodeSlashInPath) { + prepared = UriComponentEncoder.PATH_SEGMENT.encode(value.toString(), fromEncoded); + } else { + prepared = UriComponentEncoder.PATH.encode(value.toString(), fromEncoded); + } + path = path.replace(variable, prepared); + } + if (query != null) { + query = query.replace(variable, UriComponentEncoder.QUERY.encode(value.toString(), fromEncoded)); + } + } + if (fragment != null) { + fragment = fragment.replace(variable, UriComponentEncoder.FRAGMENT.encode(value.toString(), fromEncoded)); + } + return this; + } + + private void removeQueryParam(String encodedName) { + StringJoiner joiner = new StringJoiner("&"); + String[] params = query.split("&"); + for (String param : params) { + if (!param.startsWith(encodedName + '=')) { + joiner.add(param); + } + } + query = joiner.toString(); + } + + private void concatQueryParam(String encodedName, Object... values) { + StringBuilder buf = new StringBuilder(); + for (Object value : values) { + if (value != null) { + if (buf.length() != 0) { + buf.append('&'); + } + String encodedValue = new Template(UriComponentEncoder.QUERY_PARAM.encode(value.toString())).template(); + buf.append(encodedName).append('=').append(encodedValue); + } + } + if (buf.length() == 0) { + return; + } + if (query != null) { + buf.insert(0, '&').insert(0, query); + } + query = buf.toString(); + } + + private UriBuilder appendPath(String p) { + String prepared = new Template(p).template(); + if (path == null) { + path = prepared; + } else if (path.endsWith("/") && prepared.startsWith("/")) { + path += prepared.substring(1); + } else if (path.endsWith("/") ^ prepared.startsWith("/")) { + path += prepared; + } else { + path += '/' + prepared; + } + return this; + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/BodyReaderParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/BodyReaderParameter.java index bc97a30..a1fdd40 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/BodyReaderParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/BodyReaderParameter.java @@ -20,8 +20,8 @@ import javax.ws.rs.ext.MessageBodyReader; import net.cactusthorn.routing.RoutingInitializationException; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.body.reader.BodyReader; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public final class BodyReaderParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/CookieParamParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/CookieParamParameter.java index 9549246..4819ff1 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/CookieParamParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/CookieParamParameter.java @@ -15,9 +15,9 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.ext.RuntimeDelegate; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.convert.Converter; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.util.Headers; public class CookieParamParameter extends ConvertableMethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/FormParamParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/FormParamParameter.java index f5e52c3..5e4381d 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/FormParamParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/FormParamParameter.java @@ -16,8 +16,8 @@ import javax.ws.rs.core.MediaType; import net.cactusthorn.routing.RoutingInitializationException; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class FormParamParameter extends ConvertableMethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/FormPartParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/FormPartParameter.java index bf2cbbb..40be181 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/FormPartParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/FormPartParameter.java @@ -16,8 +16,8 @@ import javax.ws.rs.core.MediaType; import net.cactusthorn.routing.RoutingInitializationException; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.annotation.FormPart; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class FormPartParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/HeaderParamParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/HeaderParamParameter.java index 252b184..0336753 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/HeaderParamParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/HeaderParamParameter.java @@ -10,8 +10,8 @@ import javax.ws.rs.BadRequestException; import javax.ws.rs.HeaderParam; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class HeaderParamParameter extends ConvertableMethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/HttpHeadersParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/HttpHeadersParameter.java index e181efd..bdc1b68 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/HttpHeadersParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/HttpHeadersParameter.java @@ -22,7 +22,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.RuntimeDelegate; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.util.CaseInsensitiveMultivaluedMap; import net.cactusthorn.routing.util.Headers; import net.cactusthorn.routing.util.UnmodifiableMultivaluedMap; diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletRequestParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletRequestParameter.java index a27e59f..6e57a3e 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletRequestParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletRequestParameter.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public final class HttpServletRequestParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletResponseParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletResponseParameter.java index c6e2d50..482a566 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletResponseParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/HttpServletResponseParameter.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public final class HttpServletResponseParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/MethodInvoker.java b/core/src/main/java/net/cactusthorn/routing/invoke/MethodInvoker.java index cc42f33..13e5754 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/MethodInvoker.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/MethodInvoker.java @@ -21,7 +21,7 @@ import net.cactusthorn.routing.RoutingConfig; import net.cactusthorn.routing.Templated; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public final class MethodInvoker { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameter.java index 4379577..fde308b 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/MethodParameter.java @@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public abstract class MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/PathParamParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/PathParamParameter.java index 384919b..4630153 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/PathParamParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/PathParamParameter.java @@ -11,8 +11,8 @@ import javax.ws.rs.PathParam; import net.cactusthorn.routing.RoutingInitializationException; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class PathParamParameter extends ConvertableMethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/QueryParamParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/QueryParamParameter.java index e1162e7..888574b 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/QueryParamParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/QueryParamParameter.java @@ -10,8 +10,8 @@ import javax.ws.rs.NotFoundException; import javax.ws.rs.QueryParam; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class QueryParamParameter extends ConvertableMethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/SecurityContextParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/SecurityContextParameter.java index 197130d..a7a1562 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/SecurityContextParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/SecurityContextParameter.java @@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.SecurityContext; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class SecurityContextParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/invoke/ServletContextParameter.java b/core/src/main/java/net/cactusthorn/routing/invoke/ServletContextParameter.java index ac7bf0e..834bda9 100644 --- a/core/src/main/java/net/cactusthorn/routing/invoke/ServletContextParameter.java +++ b/core/src/main/java/net/cactusthorn/routing/invoke/ServletContextParameter.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public final class ServletContextParameter extends MethodParameter { diff --git a/core/src/main/java/net/cactusthorn/routing/resource/PathTemplateParser.java b/core/src/main/java/net/cactusthorn/routing/resource/PathTemplateParser.java index cf9a207..0c3974b 100644 --- a/core/src/main/java/net/cactusthorn/routing/resource/PathTemplateParser.java +++ b/core/src/main/java/net/cactusthorn/routing/resource/PathTemplateParser.java @@ -4,8 +4,8 @@ import javax.ws.rs.Path; -import net.cactusthorn.routing.PathTemplate; import net.cactusthorn.routing.RoutingInitializationException; +import net.cactusthorn.routing.uri.PathTemplate; public final class PathTemplateParser { diff --git a/core/src/main/java/net/cactusthorn/routing/resource/ResourceScanner.java b/core/src/main/java/net/cactusthorn/routing/resource/ResourceScanner.java index 8abe995..716ab67 100644 --- a/core/src/main/java/net/cactusthorn/routing/resource/ResourceScanner.java +++ b/core/src/main/java/net/cactusthorn/routing/resource/ResourceScanner.java @@ -17,12 +17,12 @@ import javax.annotation.security.RolesAllowed; import net.cactusthorn.routing.annotation.Template; -import net.cactusthorn.routing.PathTemplate; import net.cactusthorn.routing.RoutingConfig; import net.cactusthorn.routing.Templated; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.invoke.MethodInvoker; import net.cactusthorn.routing.invoke.MethodInvoker.ReturnObjectInfo; +import net.cactusthorn.routing.uri.PathTemplate; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class ResourceScanner { diff --git a/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java b/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java new file mode 100644 index 0000000..1d1a15d --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/uri/PathTemplate.java @@ -0,0 +1,146 @@ +package net.cactusthorn.routing.uri; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class PathTemplate extends Template { + + public static final class PathValues { + + public static final PathValues EMPTY = new PathValues(); + + private final Map values = new HashMap<>(); + + public PathValues() { + } + + public PathValues(String name, String value) { + values.put(name, value); + } + + public void put(String name, String value) { + values.put(name, value); + } + + public String value(String name) { + return values.get(name); + } + } + + public static final Comparator COMPARATOR = (o1, o2) -> { + if (o1 == null && o2 == null) { + return 0; + } + if (o1 == null) { + return 1; + } + if (o2 == null) { + return -1; + } + + int i = o2.literalCharsAmount() - o1.literalCharsAmount(); + if (i != 0) { + return i; + } + + i = o2.parametersAmount() - o1.parametersAmount(); + if (i != 0) { + return i; + } + + i = o2.regExpParamsAmount() - o1.regExpParamsAmount(); + if (i != 0) { + return i; + } + + return 0; + }; + + private static final String SIMPLE_PATTERN = "([^/]+)"; + + private static final String MULTIPLE_PARAM = "Template contain parameter \"%s\" multiple times with different pattern"; + + private int simpleParamsAmount; + private int regExpParamsAmount; + + private Pattern pattern; + + public PathTemplate(String template) { + super(template); + if (variables().isEmpty()) { + pattern = Pattern.compile(template()); + } else { + Map paramsBuf = new HashMap<>(); + String patternTemplate = template(); + for (Template.TemplateVariable variable : variables()) { + if (variable.pattern().isPresent()) { + String regExp = variable.pattern().get(); + if (paramsBuf.containsKey(variable.name()) && !regExp.equals(paramsBuf.get(variable.name()))) { + throw new IllegalArgumentException(String.format(MULTIPLE_PARAM, variable.name())); + } + regExpParamsAmount++; + if (!paramsBuf.containsKey(variable.name())) { + patternTemplate = patternTemplate.replace(variable.template(), regExp); + } + paramsBuf.put(variable.name(), regExp); + } else { + simpleParamsAmount++; + if (!paramsBuf.containsKey(variable.name())) { + patternTemplate = patternTemplate.replace(variable.template(), SIMPLE_PATTERN); + } + paramsBuf.put(variable.name(), SIMPLE_PATTERN); + } + } + pattern = Pattern.compile(patternTemplate); + } + } + + public boolean match(String path) { + if (variables().isEmpty()) { + return template().equals(path); + } + Matcher matcher = pattern.matcher(path); + return matcher.find(); + } + + public PathValues parse(String path) { + if (variables().isEmpty()) { + if (template().equals(path)) { + return PathValues.EMPTY; + } + return null; + } + Matcher matcher = pattern.matcher(path); + if (matcher.find()) { + PathValues pathValues = new PathValues(); + for (int i = 0; i < variables().size(); i++) { + pathValues.put(variables().get(i).name(), matcher.group(i + 1)); + } + return pathValues; + } + return null; + } + + public Pattern pattern() { + return pattern; + } + + public boolean isSimple() { + return variables().isEmpty(); + } + + public int simpleParamsAmount() { + return simpleParamsAmount; + } + + public int regExpParamsAmount() { + return regExpParamsAmount; + } + + public int parametersAmount() { + return simpleParamsAmount + regExpParamsAmount; + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/uri/Template.java b/core/src/main/java/net/cactusthorn/routing/uri/Template.java new file mode 100644 index 0000000..22c92df --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/uri/Template.java @@ -0,0 +1,195 @@ +package net.cactusthorn.routing.uri; + +import static java.text.CharacterIterator.DONE; + +import java.text.StringCharacterIterator; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class Template { + + public static final class TemplateVariable { + private String name; + private String template; + private Optional pattern; + + private TemplateVariable(String name) { + this(name, null); + } + + private TemplateVariable(String name, String pattern) { + this.name = name; + this.template = '{' + name + '}'; + if (pattern == null) { + this.pattern = Optional.empty(); + } else { + this.pattern = Optional.of('(' + pattern + ')'); + } + } + + public String name() { + return name; + } + + public String template() { + return template; + } + + public Optional pattern() { + return pattern; + } + + @Override public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TemplateVariable)) { + return false; + } + TemplateVariable v = (TemplateVariable) obj; + if (!name.equals(v.name())) { + return false; + } + if (!pattern.equals(v.pattern())) { + return false; + } + return true; + } + + @Override public int hashCode() { + return name.hashCode() + pattern.hashCode(); + } + } + + private final List variables = new ArrayList<>(); + private String template; + private int literalCharsAmount; + + public Template(String template) { + if (template == null) { + throw new IllegalArgumentException("Template is null"); + } + String prepared = template.trim(); + if (prepared.isEmpty()) { + throw new IllegalArgumentException("Template is empty"); + } + process(prepared); + } + + public String template() { + return template; + } + + public List variables() { + return variables; + } + + public int literalCharsAmount() { + return literalCharsAmount; + } + + private void process(String prepared) { + + StringBuilder templateBuf = new StringBuilder(); + + StringCharacterIterator it = new StringCharacterIterator(prepared); + for (char c = it.first(); c != DONE; c = it.next()) { + if (c == '{') { + TemplateVariable variable = processParam(it); + variables.add(variable); + templateBuf.append(variable.template()); + } else { + literalCharsAmount++; + templateBuf.append(c); + } + } + + template = templateBuf.toString(); + } + + private static final String WRONG_PARAM = "Template contain improperly closed parameter"; + private static final String WRONG_CHAR = "Template contain wrong character for parameter name at the possition %d"; + + private TemplateVariable processParam(StringCharacterIterator it) { + + StringBuilder param = new StringBuilder(); + int lastSpaceIndex = 0; + + char c = eraseWhitespaces(it); + do { + if (c == '{') { + throw new IllegalArgumentException(WRONG_PARAM); + } + if (c == '}') { + return new TemplateVariable(param.toString()); + } + if (c == ':') { + return new TemplateVariable(param.toString(), processRegExp(it)); + } + if (Character.isLetterOrDigit(c) || c == '_') { + if (lastSpaceIndex == 0) { + param.append(c); + } else { + throw new IllegalArgumentException(String.format(WRONG_CHAR, lastSpaceIndex + 1)); + } + } else if (Character.isWhitespace(c)) { + lastSpaceIndex = it.getIndex(); + } else { + throw new IllegalArgumentException(String.format(WRONG_CHAR, it.getIndex() + 1)); + } + c = it.next(); + } while (true); + } + + private char eraseWhitespaces(StringCharacterIterator it) { + while (true) { + char c = it.next(); + if (!Character.isWhitespace(c)) { + return c; + } + } + } + + private String processRegExp(StringCharacterIterator it) { + + int opened = 1; + StringBuilder regExp = new StringBuilder(); + + char c = eraseWhitespaces(it); + do { + if (c == '{') { + opened++; + } else if (c == '}') { + opened--; + if (opened == 0) { + return regExp.toString().trim(); + } + } + regExp.append(c); + c = it.next(); + } while (c != DONE); + + throw new IllegalArgumentException(WRONG_PARAM); + } + + @Override // + public int hashCode() { + return template.hashCode(); + } + + @Override // + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Template)) { + return false; + } + Template t = (Template) obj; + if (!template.equals(t.template())) { + return false; + } + return variables.equals(t.variables()); + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/uri/UriComponentEncoder.java b/core/src/main/java/net/cactusthorn/routing/uri/UriComponentEncoder.java new file mode 100644 index 0000000..048c616 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/uri/UriComponentEncoder.java @@ -0,0 +1,118 @@ +package net.cactusthorn.routing.uri; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; + +public enum UriComponentEncoder { + + // https://tools.ietf.org/html/rfc3986 + + SCHEME_SPECIFIC_PART(Characters.UNRESERVED, Characters.SUB_DELIMS, ":", "@"), + USER_INFO(Characters.UNRESERVED, Characters.SUB_DELIMS, ":"), + PATH(Characters.UNRESERVED, Characters.SUB_DELIMS, ":", "@", "/"), + PATH_SEGMENT(Characters.UNRESERVED, Characters.SUB_DELIMS, ":", "@"), + QUERY(Characters.UNRESERVED, Characters.SUB_DELIMS, ":", "@", "/", "?"), + QUERY_PARAM(Characters.UNRESERVED, "!", "$", "'", "(", ")", "*", "+", ",", ";", ":", "@", "/", "?"), + FRAGMENT(Characters.UNRESERVED, Characters.SUB_DELIMS, ":", "@", "/", "?"); + + private static final class Characters { + private static final String[] UNRESERVED = {"A-Z", "a-z", "0-9", "-", ".", "_", "~"}; + private static final String[] SUB_DELIMS = {"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="}; + } + + private static final int HEX_RADIX = 16; + private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + private static final byte FOUR = 4; + private static final byte OCTET_MASK = 0x0F; + private static final int BYTE_MASK = 0xFF; + + private final boolean[] valid = new boolean[Byte.MAX_VALUE + 1]; + + UriComponentEncoder(Object... objects) { + for (Object characters : objects) { + if (characters.getClass().isArray()) { + for (String pattern : (String[]) characters) { + fillValid(pattern); + } + } else { + fillValid((String) characters); + } + } + } + + public String encode(String str) { + return encode(str, true, true); + } + + public String encode(String str, boolean fromEncoded) { + return encode(str, false, fromEncoded); + } + + public String encode(String str, boolean skipTemplates, boolean fromEncoded) { + if (str == null) { + return null; + } + boolean variable = false; + StringBuilder result = new StringBuilder(str.length()); + for (int pos = 0; pos < str.length(); pos++) { + int codePoint = str.codePointAt(pos); + if (codePoint <= Byte.MAX_VALUE && valid[codePoint]) { + result.append((char) codePoint); + continue; + } + if (skipTemplates) { + if (codePoint == '{') { + variable = true; + result.append('{'); + continue; + } + if (codePoint == '}') { + variable = false; + result.append('}'); + continue; + } + if (variable) { + result.append(Character.toChars(codePoint)); + continue; + } + } + if (fromEncoded && codePoint == '%' && pos + 2 < str.length() + && Character.digit(str.codePointAt(pos + 1), HEX_RADIX) != -1 + && Character.digit(str.codePointAt(pos + 2), HEX_RADIX) != -1) { + result.append('%').append(str.charAt(pos + 1)).append(str.charAt(pos + 2)); + pos += 2; + continue; + } + if (codePoint <= Byte.MAX_VALUE) { + appendByte(result, codePoint); + } else { + appendUTF8Char(result, codePoint); + } + } + return result.toString(); + } + + private void fillValid(String pattern) { + if (pattern.length() == 1) { + valid[pattern.charAt(0)] = true; + } else { + for (char i = pattern.charAt(0); i <= pattern.charAt(2); i++) { + valid[i] = true; + } + } + } + + private static void appendByte(StringBuilder result, int b) { + result.append('%'); + result.append(HEX_DIGITS[b >> FOUR]); + result.append(HEX_DIGITS[b & OCTET_MASK]); + } + + private static void appendUTF8Char(final StringBuilder sb, int codePoint) { + ByteBuffer bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(Character.toChars(codePoint))); + while (bytes.hasRemaining()) { + appendByte(sb, bytes.get() & BYTE_MASK); + } + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/uri/UriTemplate.java b/core/src/main/java/net/cactusthorn/routing/uri/UriTemplate.java new file mode 100644 index 0000000..f39cb7d --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/uri/UriTemplate.java @@ -0,0 +1,136 @@ +package net.cactusthorn.routing.uri; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UriTemplate extends Template { + + private static final String SCHEMA = "([^?#/]+):"; + private static final String SSP = "([^/#]{1}[^#]*)"; + private static final String AUTHORITY = "(//[^?#/]*)?"; + private static final String PATH = "([^#?]*)"; + private static final String QUERY = "(\\?([^#]*))?"; + private static final String FRAGMENT = "(#(.*))?"; + + private static final int OPAQUE_SCHEMA_GROUP = 1; + private static final int OPAQUE_SSP_GROUP = 2; + private static final int OPAQUE_FRAGMENT_GROUP = 4; + private static final Pattern OPAQUE_PATTERN = Pattern.compile("^" + SCHEMA + SSP + FRAGMENT); + + private static final int HIERARCHICAL_SCHEMA_GROUP = 2; + private static final int HIERARCHICAL_AUTHORITY_GROUP = 3; + private static final int HIERARCHICAL_PATH_GROUP = 4; + private static final int HIERARCHICAL_QUERY_GROUP = 6; + private static final int HIERARCHICAL_FRAGMENT_GROUP = 8; + private static final Pattern HIERARCHICAL_PATTERN = Pattern.compile("^(" + SCHEMA + ")?" + AUTHORITY + PATH + QUERY + FRAGMENT); + + private boolean opaque; + + private String scheme; + private String schemeSpecificPart; + + private String authority; + private String userInfo; + private String host; + private String port; + + private String path; + private String query; + + private String fragment; + + public UriTemplate(String template) { + super(template); + final Matcher opaqueMatcher = OPAQUE_PATTERN.matcher(template()); + if (opaqueMatcher.matches()) { + opaque = true; + scheme = opaqueMatcher.group(OPAQUE_SCHEMA_GROUP); + schemeSpecificPart = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode(opaqueMatcher.group(OPAQUE_SSP_GROUP)); + fragment = UriComponentEncoder.FRAGMENT.encode(opaqueMatcher.group(OPAQUE_FRAGMENT_GROUP)); + } else { + final Matcher hierarchicalMatcher = HIERARCHICAL_PATTERN.matcher(template()); + hierarchicalMatcher.matches(); + scheme = hierarchicalMatcher.group(HIERARCHICAL_SCHEMA_GROUP); + parseAuthority(hierarchicalMatcher.group(HIERARCHICAL_AUTHORITY_GROUP)); + path = UriComponentEncoder.PATH.encode(hierarchicalMatcher.group(HIERARCHICAL_PATH_GROUP)); + query = UriComponentEncoder.QUERY.encode(hierarchicalMatcher.group(HIERARCHICAL_QUERY_GROUP)); + fragment = UriComponentEncoder.FRAGMENT.encode(hierarchicalMatcher.group(HIERARCHICAL_FRAGMENT_GROUP)); + } + } + + public boolean opaque() { + return opaque; + } + + public String scheme() { + return scheme; + } + + public String schemeSpecificPart() { + return schemeSpecificPart; + } + + public String authority() { + return authority; + } + + public String userInfo() { + return userInfo; + } + + public String host() { + return host; + } + + public String port() { + return port; + } + + public String path() { + return path; + } + + public String query() { + return query; + } + + public String fragment() { + return fragment; + } + + private void parseAuthority(String authorityGroup) { + if (authorityGroup == null) { + return; + } + authority = authorityGroup.substring(2); + host = authority; + final int at = authority.indexOf('@'); + if (at != -1) { + userInfo = UriComponentEncoder.USER_INFO.encode(authority.substring(0, at)); + host = authority.substring(at + 1); + } + int portAt = host.lastIndexOf("]:"); // IP6 + if (portAt != -1) { + port = host.substring(portAt + 2); + host = host.substring(0, portAt + 1); + } else { + portAt = host.lastIndexOf(":"); // IP4 + if (portAt != -1) { + port = host.substring(portAt + 1); + host = host.substring(0, portAt); + } + } + if ("".equals(userInfo)) { + userInfo = null; + } + if ("".equals(host)) { + host = null; + } + if ("".equals(port)) { + port = null; + } + if (userInfo != null || host != null || port != null) { + authority = null; + } + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java b/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java index 97c20a5..4514175 100644 --- a/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java +++ b/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java @@ -14,12 +14,12 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.RoutingConfig.ConfigProperty; import net.cactusthorn.routing.body.reader.ConvertersMessageBodyReader; import net.cactusthorn.routing.body.writer.ObjectMessageBodyWriter; import net.cactusthorn.routing.convert.ParamConverterProviderWrapperTest; import net.cactusthorn.routing.resource.ResourceScanner; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.validate.ParametersValidator; public class RoutingConfigTest { diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/LinkTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/LinkTest.java index d793303..4d39e46 100644 --- a/core/src/test/java/net/cactusthorn/routing/delegate/LinkTest.java +++ b/core/src/test/java/net/cactusthorn/routing/delegate/LinkTest.java @@ -6,6 +6,7 @@ import java.net.URISyntaxException; import javax.ws.rs.core.Link; +import javax.ws.rs.core.UriBuilder; import org.junit.jupiter.api.Test; @@ -23,19 +24,37 @@ public class LinkTest { assertEquals("; rel=preconnect; type=text/css; title=compact; aaa=\"bb cc\"", link.toString()); } - @Test public void linkUnsupported() { - Link link = Link.valueOf(""); - assertThrows(UnsupportedOperationException.class, () -> link.getUriBuilder()); + @Test public void paramNull() { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.param(null, "aa")); + assertThrows(IllegalArgumentException.class, () -> builder.param("aa", null)); + } + + @Test public void getUriBuilder() { + Link link = Link.valueOf(""); + UriBuilder uriBuilder = link.getUriBuilder(); + assertEquals("http://example.com", uriBuilder.build().toString()); + } + + @Test public void uriBuilder() { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + builder.uriBuilder(UriBuilder.fromUri("http://example.com")); + assertEquals("", builder.build().toString()); } @Test public void link() { Link.Builder builder = new LinkImpl.LinkBuilderImpl(); - Link link = builder.uri("example.com").build(); + Link link = builder.uri("http://example.com").build(); Link link2 = builder.link(link).build(); assertEquals(link.toString(), link2.toString()); assertNotEquals(link.hashCode(), link2.hashCode()); } + @Test public void relNull() { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.rel(null)); + } + @Test public void rel() { Link.Builder builder = new LinkImpl.LinkBuilderImpl(); Link link = builder.link("; rel=\"preconnect\"").build(); @@ -53,12 +72,22 @@ public class LinkTest { assertTrue(link.getRels().isEmpty()); } + @Test public void titleNull() { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.title(null)); + } + @Test public void title() { Link.Builder builder = new LinkImpl.LinkBuilderImpl(); Link link = builder.link("; title=compact").build(); assertEquals("compact", link.getTitle()); } + @Test public void typeNull() { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.type(null)); + } + @Test public void type() { Link.Builder builder = new LinkImpl.LinkBuilderImpl(); Link link = builder.link("; type=text/css").build(); @@ -72,12 +101,30 @@ public class LinkTest { assertThrows(IllegalArgumentException.class, () -> builder.link("; type-text/css")); } - @Test public void builderUnsupported() { - Link.Builder builder = new LinkImpl.LinkBuilderImpl(); - assertThrows(UnsupportedOperationException.class, () -> builder.baseUri((URI) null)); - assertThrows(UnsupportedOperationException.class, () -> builder.baseUri((String) null)); - assertThrows(UnsupportedOperationException.class, () -> builder.uriBuilder(null)); - assertThrows(UnsupportedOperationException.class, () -> builder.buildRelativized(null)); + @Test public void template() { + Link link = Link.fromUri("http://{host}/root/customers/{id}").rel("update").type("text/plain").build("localhost", "1234"); + assertEquals("; rel=update; type=text/plain", link.toString()); + } + + @Test public void buildRelativized() throws URISyntaxException { + Link link = Link.fromUri("a/d/e").rel("update").type("text/plain").baseUri("http://localhost/") + .buildRelativized(new URI("http://localhost/a")); + assertEquals("; rel=update; type=text/plain", link.toString()); } + @Test public void buildRelativized2() throws URISyntaxException { + Link link = Link.fromUri("a/d/e").rel("update").type("text/plain").buildRelativized(new URI("a")); + assertEquals("; rel=update; type=text/plain", link.toString()); + } + + @Test public void buildRelativized3() throws URISyntaxException { + Link link = Link.fromUri("a/d/e").rel("update").type("text/plain").baseUri(new URI("http://localhost/")) + .buildRelativized(new URI("http://localhost/a")); + assertEquals("; rel=update; type=text/plain", link.toString()); + } + + @Test public void buildRelativizedNull() throws URISyntaxException { + Link.Builder builder = new LinkImpl.LinkBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.buildRelativized(null)); + } } diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/RuntimeDelegateImplTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/RuntimeDelegateImplTest.java index 8dd23fd..345fa05 100644 --- a/core/src/test/java/net/cactusthorn/routing/delegate/RuntimeDelegateImplTest.java +++ b/core/src/test/java/net/cactusthorn/routing/delegate/RuntimeDelegateImplTest.java @@ -5,6 +5,7 @@ import javax.ws.rs.core.Link; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.Variant; import org.junit.jupiter.api.Test; @@ -36,9 +37,14 @@ public void createLinkBuilder() { assertNotNull(builder); } + @Test // + public void createUriBuilder() { + UriBuilder uriBuilder = IMPL.createUriBuilder(); + assertNotNull(uriBuilder); + } + @Test // public void checkUnsupported() { - assertThrows(UnsupportedOperationException.class, () -> IMPL.createUriBuilder()); assertThrows(UnsupportedOperationException.class, () -> IMPL.createEndpoint(null, null)); } } diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderBuildTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderBuildTest.java new file mode 100644 index 0000000..c1b19bd --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderBuildTest.java @@ -0,0 +1,85 @@ +package net.cactusthorn.routing.delegate.uribuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URI; +import java.util.HashMap; + +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.delegate.UriBuilderImpl; + +public class UriBuilderBuildTest { + + @Test public void buildFromMapNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.buildFromMap(null)); + assertThrows(IllegalArgumentException.class, () -> builder.buildFromMap(null, true)); + assertThrows(IllegalArgumentException.class, () -> builder.buildFromEncodedMap(null)); + } + + @Test public void buildEmpty() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("http://a.com"); + assertEquals("http://a.com", builder.build(new Object[0]).toString()); + assertEquals("http://a.com", builder.build().toString()); + assertEquals("http://a.com", builder.build(new Object[0], true).toString()); + assertEquals("http://a.com", builder.buildFromEncoded(new Object[0]).toString()); + } + + @Test public void buildURIException() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("htt p://a.com"); + assertThrows(UriBuilderException.class, () -> builder.build((Object[]) null)); + assertThrows(UriBuilderException.class, () -> builder.build(null, true)); + assertThrows(UriBuilderException.class, () -> builder.buildFromEncoded((Object[]) null)); + } + + @Test public void buildMapURIException() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("htt p://a.com"); + assertThrows(UriBuilderException.class, () -> builder.buildFromMap(new HashMap<>())); + assertThrows(UriBuilderException.class, () -> builder.buildFromMap(new HashMap<>(), true)); + assertThrows(UriBuilderException.class, () -> builder.buildFromEncodedMap(new HashMap<>())); + } + + @Test public void build() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{schema}://{userinfo}@{host}:{port}/{path}?{p}={v}&{p}={port}#{f}"); + URI uri = builder.build("http", "aa:bb", "a.com", 8080, "xyz", "var", "value", "fragment"); + assertEquals("http://aa:bb@a.com:8080/xyz?var=value&var=8080#fragment", uri.toString()); + uri = builder.build("https", "aa2:bb2", "a.com", 8080, "xyz", "var", "val%20ue", "fragment"); + assertEquals("https://aa2:bb2@a.com:8080/xyz?var=val%2520ue&var=8080#fragment", uri.toString()); + } + + @Test public void buildSlash() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{schema}://{userinfo}@{host}:{port}/{path}?{p}={v}&{p}={port}#{f}"); + Object[] vars = new Object[] { "http", "aa:bb", "a.com", 8080, "xyz/ABC", "var", "value", "fragment" }; + URI uri = builder.build(vars, true); + assertEquals("http://aa:bb@a.com:8080/xyz%2FABC?var=value&var=8080#fragment", uri.toString()); + } + + @Test public void buildFromEncoded() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{schema}://{userinfo}@{host}:{port}/{path}?{p}={v}&{p}={port}#{f}"); + URI uri = builder.buildFromEncoded("https", "aa@2:bb2", "a.com", 8080, "xyz", "var", "val%20ue", "fragment"); + assertEquals("https://aa%402:bb2@a.com:8080/xyz?var=val%20ue&var=8080#fragment", uri.toString()); + } + + @Test public void buildNotEnoughValues() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{schema}://{userinfo}@{host}:{port}/{path}?{p}={v}&{p}={port}#{f}"); + assertThrows(IllegalArgumentException.class, () -> builder.build("http", "aa:bb", "a.com", 8080, "xyz", "var", "value")); + } + + @Test public void buildWithNullValue() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{schema}://{userinfo}@{host}:{port}/{path}?{p}={v}&{p}={port}#{f}"); + assertThrows(IllegalArgumentException.class, () -> builder.build("http", null, "a.com")); + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderPathTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderPathTest.java new file mode 100644 index 0000000..83917c5 --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderPathTest.java @@ -0,0 +1,171 @@ +package net.cactusthorn.routing.delegate.uribuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.ws.rs.Path; +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.delegate.UriBuilderImpl; + +public class UriBuilderPathTest { + + public static class NotResource { + } + + @Path("xyz") public static class Resource { + public void xx() { + } + + @Path("abc") public void zzz() { + } + + @Path("abc2") public void ttt() { + } + + @Path("abc3") public void ttt(int i) { + } + } + + @Test public void pathOpaque() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com"); + assertThrows(IllegalArgumentException.class, () -> builder.replacePath("aa")); + assertThrows(IllegalArgumentException.class, () -> builder.path("aa")); + assertThrows(IllegalArgumentException.class, () -> builder.path(Resource.class)); + assertThrows(IllegalArgumentException.class, () -> builder.path(Resource.class, "zzz")); + assertThrows(IllegalArgumentException.class, () -> builder.path(findMethod(Resource.class, "zzz"))); + assertThrows(IllegalArgumentException.class, () -> builder.segment("aaaaa")); + } + + @Test public void replacePath() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?aa=bb").replacePath(null); + assertEquals("?aa=bb", builder.build().toString()); + assertEquals("/aa/ccc/ddd?aa=bb", builder.replacePath("/aa/ccc/ddd").build().toString()); + } + + @Test public void appendPath() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com").path("xxxxx").build(); + assertEquals("/xxxxx", uri.getPath()); + } + + @Test public void appendPath2() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com/").path("xxxxx").build(); + assertEquals("/xxxxx", uri.getPath()); + } + + @Test public void appendPath3() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com/aa/").path("/xxxxx").build(); + assertEquals("/aa/xxxxx", uri.getPath()); + } + + @Test public void appendPath4() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com/aa/").path("xxxxx").build(); + assertEquals("/aa/xxxxx", uri.getPath()); + } + + @Test public void appendPath5() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com/aa").path("/xxxxx").build(); + assertEquals("/aa/xxxxx", uri.getPath()); + } + + @Test public void appendPathMulti() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com").path("aa/bb").path("cc").build(); + assertEquals("/aa/bb/cc", uri.getPath()); + } + + @Test public void appendPathNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path((String) null)); + } + + @Test public void appendPathClass() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("http://java-net@java.sun.com").path(Resource.class).build(); + assertEquals("/xyz", uri.getPath()); + } + + @Test public void appendPathClassNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path((Class) null)); + } + + @Test public void appendPathClassNotResource() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path(NotResource.class)); + } + + @Test public void appendPathClassMethodNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path(null, "yy")); + assertThrows(IllegalArgumentException.class, () -> builder.path(NotResource.class, null)); + } + + @Test public void appendPathClassMethodNotExists() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path(Resource.class, "yyy")); + assertThrows(IllegalArgumentException.class, () -> builder.path(Resource.class, "xx")); + } + + @Test public void appendPathClassMethod() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.path(Resource.class, "zzz").build(); + assertEquals("abc", uri.getPath()); + } + + @Test public void appendPathClassMethodMultiple() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path(Resource.class, "ttt").build()); + } + + @Test public void appendPathMethodNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path((Method) null)); + } + + @Test public void appendPathMethodNotExists() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.path(findMethod(Resource.class, "xx"))); + } + + @Test public void appendPathMethod() { + UriBuilder builder = new UriBuilderImpl(); + Method method = findMethod(Resource.class, "zzz"); + URI uri = builder.path(method).build(); + assertEquals("abc", uri.getPath()); + } + + @Test public void segments() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc").segment("aaa", "a/b/c", "wwww"); + assertEquals("/abc/aaa/a%2Fb%2Fc/wwww", builder.build().toString()); + } + + @Test public void segmentsNull() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.segment((String[]) null)); + assertThrows(IllegalArgumentException.class, () -> builder.segment("aaaa", null, "bbbb")); + } + + protected Method findMethod(Class clazz, String methodName) { + for (Method method : clazz.getMethods()) { + if (methodName.equals(method.getName())) { + return method; + } + } + return null; + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderQueryTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderQueryTest.java new file mode 100644 index 0000000..19b27aa --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderQueryTest.java @@ -0,0 +1,82 @@ +package net.cactusthorn.routing.delegate.uribuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.delegate.UriBuilderImpl; + +public class UriBuilderQueryTest { + + @Test public void pathOpaque() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com"); + assertThrows(IllegalArgumentException.class, () -> builder.replaceQuery("aa=bb")); + assertThrows(IllegalArgumentException.class, () -> builder.queryParam("aa", "bb")); + assertThrows(IllegalArgumentException.class, () -> builder.replaceQueryParam("aa", "bb")); + } + + @Test public void replaceQuery() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?aa=bb").replaceQuery("z=c&b=a"); + assertEquals("/abc?z=c&b=a", builder.build().toString()); + } + + @Test public void replaceQueryNull() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?aa=bb").replaceQuery(null); + assertEquals("/abc", builder.build().toString()); + } + + @Test public void queryParamNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.queryParam("aaa", (Object[]) null)); + assertThrows(IllegalArgumentException.class, () -> builder.queryParam(null, "aaaa")); + assertThrows(IllegalArgumentException.class, () -> builder.replaceQueryParam(null, "aaaa")); + } + + @Test public void queryParamEmpty() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?aa=bb").queryParam("bbbb", new Object[0]); + assertEquals("/abc?aa=bb", builder.build().toString()); + } + + @Test public void queryParamNew() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc").queryParam("bb", 10, "aaaa", null, 20); + assertEquals("/abc?bb=10&bb=aaaa&bb=20", builder.build().toString()); + } + + @Test public void queryParam() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?ww=zz").queryParam("bb", 10, "aaaa", null, 20); + assertEquals("/abc?ww=zz&bb=10&bb=aaaa&bb=20", builder.build().toString()); + } + + @Test public void queryParamAllNull() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?ww=zz").queryParam("bb", new Object[] { null, null }); + assertEquals("/abc?ww=zz", builder.build().toString()); + } + + @Test public void replaceQueryParam() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?ww=zz&tt=aÜb").replaceQueryParam("ww", new Object[] { "ab", "tt tt" }); + assertEquals("/abc?tt=a%C3%9Cb&ww=ab&ww=tt%20tt", builder.build().toString()); + } + + @Test public void replaceQueryParamDelete() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?w w=zz&tt=aÜb").replaceQueryParam("w w", (Object[]) null); + assertEquals("/abc?tt=a%C3%9Cb", builder.build().toString()); + } + + @Test public void replaceQueryParamDelete2() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/abc?w w=zz&tt=aÜb").replaceQueryParam("w w", new Object[0]); + assertEquals("/abc?tt=a%C3%9Cb", builder.build().toString()); + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderResolveTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderResolveTest.java new file mode 100644 index 0000000..16542c7 --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderResolveTest.java @@ -0,0 +1,85 @@ +package net.cactusthorn.routing.delegate.uribuilder; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.delegate.UriBuilderImpl; + +public class UriBuilderResolveTest { + + @Test public void resolveTemplateSignleNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.resolveTemplate(null, "aa")); + assertThrows(IllegalArgumentException.class, () -> builder.resolveTemplate("aa", null)); + assertThrows(IllegalArgumentException.class, () -> builder.resolveTemplates(null)); + assertThrows(IllegalArgumentException.class, () -> builder.resolveTemplates(null, true)); + assertThrows(IllegalArgumentException.class, () -> builder.resolveTemplatesFromEncoded(null)); + } + + @Test public void resolveTemplateSingle() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{ var }:a@a.com#ss{var}").resolveTemplate("var", "mailto"); + assertEquals("mailto:a@a.com#ssmailto", builder.build().toString()); + } + + @Test public void resolveTemplateSingle2() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com#ss").scheme("f{ var : \\d{10} }").resolveTemplate("var", "ile"); + assertEquals("file:a@a.com#ss", builder.build().toString()); + } + + @Test public void resolveTemplateFull() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("{ var }://{ var}@{var }/{var}?{var}={var}#{var}").resolveTemplate("var", "file"); + assertEquals("file://file@file/file?file=file#file", builder.build().toString()); + } + + @Test public void resolveTemplateSpash() { + UriBuilder builder = new UriBuilderImpl(); + builder.replacePath("/{var}").resolveTemplate("var", "aa/b%20b", true); + assertEquals("/aa%2Fb%2520b", builder.build().toString()); + } + + @Test public void resolveTemplateFromEncoded() { + UriBuilder builder = new UriBuilderImpl(); + builder.replacePath("/{var}").resolveTemplateFromEncoded("var", "a%20a/bb"); + assertEquals("/a%20a/bb", builder.build().toString()); + } + + @Test public void resolveTemplates() { + UriBuilder builder = new UriBuilderImpl(); + Map vars = new HashMap<>(); + vars.put("var", "file"); + builder.uri("{ var }://{ var}@{var }/{var}?{var}={var}#{var}").resolveTemplates(vars); + assertEquals("file://file@file/file?file=file#file", builder.build().toString()); + } + + @Test public void resolveTemplatesSpash() { + UriBuilder builder = new UriBuilderImpl(); + Map vars = new HashMap<>(); + vars.put("var", "aa/b%20b"); + builder.replacePath("/{var}").resolveTemplates(vars, true); + assertEquals("/aa%2Fb%2520b", builder.build().toString()); + } + + @Test public void resolveTemplatesFromEncoded() { + UriBuilder builder = new UriBuilderImpl(); + Map vars = new HashMap<>(); + vars.put("var", "a%20a/bb"); + builder.replacePath("/{var}").resolveTemplatesFromEncoded(vars); + assertEquals("/a%20a/bb", builder.build().toString()); + } + + @Test public void resolveAuthority() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("http://@/{var}").resolveTemplate("var", "path"); + assertEquals("http://@/path", builder.build().toString()); + } + +} diff --git a/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderSimpleTest.java b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderSimpleTest.java new file mode 100644 index 0000000..fb4c43f --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/delegate/uribuilder/UriBuilderSimpleTest.java @@ -0,0 +1,205 @@ +package net.cactusthorn.routing.delegate.uribuilder; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import java.net.URISyntaxException; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.delegate.UriBuilderImpl; + +public class UriBuilderSimpleTest { + + @Test public void builderClone() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("//user@cactusthorn.net:8080"); + UriBuilder cloned = builder.clone(); + assertNotEquals(builder.hashCode(), cloned.hashCode()); + assertEquals(builder.build(), cloned.build()); + } + + @Test public void matrix() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(UnsupportedOperationException.class, () -> builder.replaceMatrix(null)); + assertThrows(UnsupportedOperationException.class, () -> builder.matrixParam(null)); + assertThrows(UnsupportedOperationException.class, () -> builder.replaceMatrixParam(null)); + } + + @Test public void uriNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.uri((String) null)); + assertThrows(IllegalArgumentException.class, () -> builder.uri((URI) null)); + } + + @Test public void uri() throws URISyntaxException { + URI expected = new URI("http", "user", "host", 10, "/aaa", "bb=cc", "fragment"); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("https://userInfo@/ccc"); + builder.uri(expected); + assertEquals(expected, builder.build()); + } + + @Test public void uri2() throws URISyntaxException { + URI source = new URI(null, "user", "host", 10, "/aaa", "", null); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("https://userInfo@/ccc#abc"); + builder.uri(source); + assertEquals("https://user@host:10/aaa#abc", builder.build().toString()); + } + + @Test public void uri3() throws URISyntaxException { + URI source = new URI("aaa"); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("https://userInfo@/ccc#abc"); + builder.uri(source); + assertEquals("https://userInfo@/aaa#abc", builder.build().toString()); + } + + @Test public void uriOpaque() throws URISyntaxException { + URI source = new URI("mailto", "a@a.com", null); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("https://userInfo@/ccc#abc"); + builder.uri(source); + assertEquals("mailto:a@a.com#abc", builder.build().toString()); + } + + @Test public void uriAuthority() throws URISyntaxException { + URI source = new URI(null, "@", null, null, null); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("https://userInfo@/ccc#abc"); + builder.uri(source); + assertEquals("https://@/ccc#abc", builder.build().toString()); + } + + @Test public void uriPath() throws URISyntaxException { + URI source = new URI(null, null, "abc", "aa=bb", null); + UriBuilder builder = new UriBuilderImpl(); + builder.uri("/ccc#abc"); + builder.uri(source); + assertEquals("abc?aa=bb#abc", builder.build().toString()); + } + + @Test public void schemeHost() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.scheme("http").host("cactusthorn.net").build(); + assertEquals("http", uri.getScheme()); + } + + @Test public void sspOpaque() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.scheme("mailto").schemeSpecificPart("java-net@java.sun.com").build(); + assertEquals("java-net@java.sun.com", uri.getSchemeSpecificPart()); + } + + @Test public void sspHierarchical() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.schemeSpecificPart("//user@cactusthorn.net:8080").build(); + assertEquals("user", uri.getUserInfo()); + assertEquals("cactusthorn.net", uri.getHost()); + assertEquals(8080, uri.getPort()); + assertEquals("", uri.getPath()); + } + + @Test public void sspHierarchical2() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.schemeSpecificPart("//@/abc").build(); + assertEquals("@", uri.getAuthority()); + assertNull(uri.getUserInfo()); + assertNull(uri.getHost()); + assertEquals(-1, uri.getPort()); + assertEquals("/abc", uri.getPath()); + } + + @Test public void sspNull() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.schemeSpecificPart(null)); + } + + @Test public void sspFragment() { + UriBuilder builder = new UriBuilderImpl(); + builder.scheme("mailto"); + assertThrows(IllegalArgumentException.class, () -> builder.schemeSpecificPart("java-net@java.sun.com#abc")); + } + + @Test public void userInfoOpaque() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com"); + assertThrows(IllegalArgumentException.class, () -> builder.userInfo("user")); + } + + @Test public void userInfo() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("/path1/path2").userInfo("user").host("cactusthorn.net").build(); + assertEquals("//user@cactusthorn.net/path1/path2", uri.toString()); + uri = builder.userInfo(null).build(); + assertEquals("//cactusthorn.net/path1/path2", uri.toString()); + } + + @Test public void hostOpaque() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com"); + assertThrows(IllegalArgumentException.class, () -> builder.host("cactusthorn.net")); + } + + @Test public void host() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.uri("/path1/path2").host("cactusthorn.net").build(); + assertEquals("//cactusthorn.net/path1/path2", uri.toString()); + uri = builder.host(null).build(); + assertEquals("/path1/path2", uri.toString()); + } + + @Test public void portOpaque() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailto:a@a.com"); + assertThrows(IllegalArgumentException.class, () -> builder.port(8080)); + } + + @Test public void portWrong() { + UriBuilder builder = new UriBuilderImpl(); + assertThrows(IllegalArgumentException.class, () -> builder.port(-10)); + } + + @Test public void port() { + UriBuilder builder = new UriBuilderImpl(); + URI uri = builder.host("cactusthorn.net").port(8080).build(); + assertEquals("//cactusthorn.net:8080", uri.toString()); + uri = builder.port(-1).build(); + assertEquals("//cactusthorn.net", uri.toString()); + } + + @Test public void fragment() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailtop:a@a.com#abc").fragment("ww rrr"); + assertEquals("mailtop:a@a.com#ww%20rrr", builder.build().toString()); + } + + @Test public void fragmentDelete() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("mailtop:a@a.com#abc").fragment(null); + assertEquals("mailtop:a@a.com", builder.build().toString()); + } + + @Test public void autority() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + URI uri = new URI("http://@/path"); + builder.uri(uri); + assertEquals("http://@/path", builder.build().toString()); + } + + @Test public void autority2() throws URISyntaxException { + UriBuilder builder = new UriBuilderImpl(); + URI uri = new URI("http://@:80/path"); + builder.uri(uri); + assertEquals("http://@:80/path", builder.build().toString()); + } + + @Test public void schemeNull() { + UriBuilder builder = new UriBuilderImpl(); + builder.uri("http://a.com").scheme(null); + assertEquals("//a.com", builder.build().toString()); + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java b/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java index c20ff0f..998bea0 100644 --- a/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java +++ b/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java @@ -34,7 +34,7 @@ import net.cactusthorn.routing.RoutingConfig; import net.cactusthorn.routing.ServletTestInputStream; import net.cactusthorn.routing.invoke.MethodInvoker.ReturnObjectInfo; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.validate.ParametersValidator; public class MethodInvokerTest extends InvokeTestAncestor { diff --git a/core/src/test/java/net/cactusthorn/routing/invoke/MethodParameterTest.java b/core/src/test/java/net/cactusthorn/routing/invoke/MethodParameterTest.java index 09c24cc..945d2f9 100644 --- a/core/src/test/java/net/cactusthorn/routing/invoke/MethodParameterTest.java +++ b/core/src/test/java/net/cactusthorn/routing/invoke/MethodParameterTest.java @@ -25,10 +25,10 @@ import org.junit.jupiter.api.Test; import net.cactusthorn.routing.ComponentProvider; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.RoutingConfig; import net.cactusthorn.routing.RoutingInitializationException; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class MethodParameterTest extends InvokeTestAncestor { diff --git a/core/src/test/java/net/cactusthorn/routing/invoke/PathParamParameterTest.java b/core/src/test/java/net/cactusthorn/routing/invoke/PathParamParameterTest.java index eda2b90..dedb597 100644 --- a/core/src/test/java/net/cactusthorn/routing/invoke/PathParamParameterTest.java +++ b/core/src/test/java/net/cactusthorn/routing/invoke/PathParamParameterTest.java @@ -16,7 +16,7 @@ import org.junit.jupiter.params.provider.ValueSource; import net.cactusthorn.routing.RoutingInitializationException; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.ComponentProvider; import net.cactusthorn.routing.RoutingConfig; diff --git a/core/src/test/java/net/cactusthorn/routing/resource/ScannerTest.java b/core/src/test/java/net/cactusthorn/routing/resource/ScannerTest.java index b3bc149..5d80855 100644 --- a/core/src/test/java/net/cactusthorn/routing/resource/ScannerTest.java +++ b/core/src/test/java/net/cactusthorn/routing/resource/ScannerTest.java @@ -11,11 +11,11 @@ import org.junit.jupiter.api.Test; import net.cactusthorn.routing.ComponentProvider; -import net.cactusthorn.routing.PathTemplate; -import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.RoutingConfig; import net.cactusthorn.routing.annotation.Template; import net.cactusthorn.routing.resource.ResourceScanner.Resource; +import net.cactusthorn.routing.uri.PathTemplate; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; import net.cactusthorn.routing.util.Headers; import javax.ws.rs.GET; diff --git a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateComparatorTest.java b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateComparatorTest.java similarity index 95% rename from core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateComparatorTest.java rename to core/src/test/java/net/cactusthorn/routing/uri/PathTemplateComparatorTest.java index db48c41..97c8338 100644 --- a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateComparatorTest.java +++ b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateComparatorTest.java @@ -1,11 +1,9 @@ -package net.cactusthorn.routing.pathtemplate; +package net.cactusthorn.routing.uri; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; -import net.cactusthorn.routing.PathTemplate; - public class PathTemplateComparatorTest { @Test // diff --git a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateMatchesTest.java b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateMatchesTest.java similarity index 94% rename from core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateMatchesTest.java rename to core/src/test/java/net/cactusthorn/routing/uri/PathTemplateMatchesTest.java index d5299c3..8758df5 100644 --- a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateMatchesTest.java +++ b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateMatchesTest.java @@ -1,11 +1,10 @@ -package net.cactusthorn.routing.pathtemplate; +package net.cactusthorn.routing.uri; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; -import net.cactusthorn.routing.PathTemplate; -import net.cactusthorn.routing.PathTemplate.PathValues; +import net.cactusthorn.routing.uri.PathTemplate.PathValues; public class PathTemplateMatchesTest { diff --git a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateParamTest.java b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateParamTest.java similarity index 75% rename from core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateParamTest.java rename to core/src/test/java/net/cactusthorn/routing/uri/PathTemplateParamTest.java index 8bbcaf4..2074972 100644 --- a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateParamTest.java +++ b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateParamTest.java @@ -1,14 +1,13 @@ -package net.cactusthorn.routing.pathtemplate; +package net.cactusthorn.routing.uri; import static org.junit.jupiter.api.Assertions.*; import java.util.*; import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import net.cactusthorn.routing.PathTemplate; - public class PathTemplateParamTest { private static final Set PARAM_NAMES = new HashSet<>(Arrays.asList("id", "ddd", "some")); @@ -16,7 +15,7 @@ public class PathTemplateParamTest { @Test // public void single() { PathTemplate pt = new PathTemplate("/api/{id}/bbb"); - List parameters = pt.parameters(); + List parameters = pt.variables(); assertEquals(1, parameters.size()); assertFalse(pt.isSimple()); } @@ -24,36 +23,36 @@ public void single() { @Test // public void simple() { PathTemplate pt = new PathTemplate("/api/te{id}st/som{ddd}ething{some}"); - List parameters = pt.parameters(); - assertEquals(3, parameters.size()); - assertTrue(parameters.containsAll(PARAM_NAMES)); + assertEquals(3, pt.variables().size()); + Set names = pt.variables().stream().map(v -> v.name()).collect(Collectors.toSet()); + assertTrue(names.containsAll(PARAM_NAMES)); assertFalse(pt.isSimple()); } @Test // public void withSpaces() { PathTemplate pt = new PathTemplate("/api/te{id }st/som{ ddd}ething{ some\t}"); - List parameters = pt.parameters(); - assertEquals(3, parameters.size()); - assertTrue(parameters.containsAll(PARAM_NAMES)); + assertEquals(3, pt.variables().size()); + Set names = pt.variables().stream().map(v -> v.name()).collect(Collectors.toSet()); + assertTrue(names.containsAll(PARAM_NAMES)); assertFalse(pt.isSimple()); } @Test // public void withRegExp() { PathTemplate pt = new PathTemplate("/api/te{id : ab}st/som{ ddd :ffff}ething{ some: rtz\t}"); - List parameters = pt.parameters(); - assertEquals(3, parameters.size()); - assertTrue(parameters.containsAll(PARAM_NAMES)); + assertEquals(3, pt.variables().size()); + Set names = pt.variables().stream().map(v -> v.name()).collect(Collectors.toSet()); + assertTrue(names.containsAll(PARAM_NAMES)); assertFalse(pt.isSimple()); } @Test // public void mixed() { PathTemplate pt = new PathTemplate("/api/te{id}st/som{ ddd:ffff}ething{some: rtz\t}"); - List parameters = pt.parameters(); - assertEquals(3, parameters.size()); - assertTrue(parameters.containsAll(PARAM_NAMES)); + assertEquals(3, pt.variables().size()); + Set names = pt.variables().stream().map(v -> v.name()).collect(Collectors.toSet()); + assertTrue(names.containsAll(PARAM_NAMES)); assertFalse(pt.isSimple()); } @@ -107,6 +106,6 @@ public void patternMultipleNotSame() { @Test // public void patternMultiple() { PathTemplate pt = new PathTemplate("/api/test{ ddd:aaaa }/som{ddd : aaaa}"); - assertEquals(2, pt.regExpParametersAmount()); + assertEquals(2, pt.regExpParamsAmount()); } } diff --git a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateSimpleTest.java b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateSimpleTest.java similarity index 86% rename from core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateSimpleTest.java rename to core/src/test/java/net/cactusthorn/routing/uri/PathTemplateSimpleTest.java index 8bc346d..d1900fa 100644 --- a/core/src/test/java/net/cactusthorn/routing/pathtemplate/PathTemplateSimpleTest.java +++ b/core/src/test/java/net/cactusthorn/routing/uri/PathTemplateSimpleTest.java @@ -1,13 +1,9 @@ -package net.cactusthorn.routing.pathtemplate; +package net.cactusthorn.routing.uri; import static org.junit.jupiter.api.Assertions.*; -import java.net.URISyntaxException; - import org.junit.jupiter.api.Test; -import net.cactusthorn.routing.PathTemplate; - public class PathTemplateSimpleTest { @Test // @@ -45,8 +41,8 @@ public void withSimpleParams() { public void withParams() { PathTemplate pt = new PathTemplate("/api/te{id : xxxx}st/som{ddd}ething{some: xxx}"); assertEquals(19, pt.literalCharsAmount()); - assertEquals(1, pt.simpleParametersAmount()); - assertEquals(2, pt.regExpParametersAmount()); + assertEquals(1, pt.simpleParamsAmount()); + assertEquals(2, pt.regExpParamsAmount()); assertFalse(pt.isSimple()); } @@ -60,12 +56,6 @@ public void wrongCharacter() { assertThrows(IllegalArgumentException.class, () -> new PathTemplate("/api/te{ id,ddd }ething")); } - @Test // - public void uri() { - Exception exception = assertThrows(IllegalArgumentException.class, () -> new PathTemplate("sert/}dddd")); - assertEquals(URISyntaxException.class, exception.getCause().getClass()); - } - @Test // public void wrongOpenBrace() { assertThrows(IllegalArgumentException.class, () -> new PathTemplate("/api/te{ id { }ething")); diff --git a/core/src/test/java/net/cactusthorn/routing/uri/UriComponentEncoderTest.java b/core/src/test/java/net/cactusthorn/routing/uri/UriComponentEncoderTest.java new file mode 100644 index 0000000..7c72ad0 --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/uri/UriComponentEncoderTest.java @@ -0,0 +1,42 @@ +package net.cactusthorn.routing.uri; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class UriComponentEncoderTest { + + @Test public void strNull() { + assertNull(UriComponentEncoder.SCHEME_SPECIFIC_PART.encode(null)); + } + + @Test public void skipTemplateVariables() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("v@vvvÜ{aÜb} dd"); + assertEquals("v@vvv%C3%9C{aÜb}%20dd", result); + } + + @Test public void notSkipTemplateVariables() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("v@vvvÜ{aÜb} dd", false); + assertEquals("v@vvv%C3%9C%7Ba%C3%9Cb%7D%20dd", result); + } + + @Test public void alreadyEncoded() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("a%2F b"); + assertEquals("a%2F%20b", result); + } + + @Test public void percent() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("ab%F"); + assertEquals("ab%25F", result); + } + + @Test public void percent2() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("ab%2ZW"); + assertEquals("ab%252ZW", result); + } + + @Test public void percent3() { + String result = UriComponentEncoder.SCHEME_SPECIFIC_PART.encode("ab%ZAW"); + assertEquals("ab%25ZAW", result); + } +} diff --git a/core/src/test/java/net/cactusthorn/routing/uri/UriTemplateTest.java b/core/src/test/java/net/cactusthorn/routing/uri/UriTemplateTest.java new file mode 100644 index 0000000..f610870 --- /dev/null +++ b/core/src/test/java/net/cactusthorn/routing/uri/UriTemplateTest.java @@ -0,0 +1,114 @@ +package net.cactusthorn.routing.uri; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.routing.uri.Template.TemplateVariable; + +public class UriTemplateTest { + + @Test public void opaque() { + UriTemplate uriTemplate = new UriTemplate("mail{var1}to:java-netÜ@java.sun.com"); + assertTrue(uriTemplate.opaque()); + assertEquals("mail{var1}to", uriTemplate.scheme()); + assertEquals("java-net%C3%9C@java.sun.com", uriTemplate.schemeSpecificPart()); + assertNull(uriTemplate.fragment()); + } + + @Test public void opaqueFragment() { + UriTemplate uriTemplate = new UriTemplate("mail{var1}to:java-net@{ var2 : \\d{2} }#xy zw"); + assertTrue(uriTemplate.opaque()); + assertEquals("mail{var1}to", uriTemplate.scheme()); + assertEquals("java-net@{var2}", uriTemplate.schemeSpecificPart()); + assertEquals("xy%20zw", uriTemplate.fragment()); + } + + @Test public void hierarchical() { + UriTemplate uriTemplate = new UriTemplate("http://java.sun.com/j2se/1.3/"); + assertFalse(uriTemplate.opaque()); + assertEquals("http", uriTemplate.scheme()); + assertNull(uriTemplate.userInfo()); + assertEquals("java.sun.com", uriTemplate.host()); + assertEquals("/j2se/1.3/", uriTemplate.path()); + } + + @Test public void hierarchicalUserInfo() { + UriTemplate uriTemplate = new UriTemplate("http://aaü@java.sun.com/j2se/1.3/"); + assertFalse(uriTemplate.opaque()); + assertEquals("http", uriTemplate.scheme()); + assertEquals("aa%C3%BC", uriTemplate.userInfo()); + assertEquals("java.sun.com", uriTemplate.host()); + assertEquals("/j2se/1.3/", uriTemplate.path()); + } + + @Test public void hierarchicalQuery() { + UriTemplate uriTemplate = new UriTemplate("//@:?aa=bb"); + assertFalse(uriTemplate.opaque()); + assertNull(uriTemplate.scheme()); + assertNull(uriTemplate.userInfo()); + assertNull(uriTemplate.host()); + assertNull(uriTemplate.port()); + assertEquals("@:", uriTemplate.authority()); + assertEquals("aa=bb", uriTemplate.query()); + } + + @Test public void hierarchicalPath() { + UriTemplate uriTemplate = new UriTemplate("j2se/{var2:\\d{2}}"); + assertFalse(uriTemplate.opaque()); + assertNull(uriTemplate.scheme()); + assertNull(uriTemplate.userInfo()); + assertNull(uriTemplate.host()); + assertEquals("j2se/{var2}", uriTemplate.path()); + assertNull(uriTemplate.query()); + } + + @Test public void hierarchicalIP6() { + UriTemplate uriTemplate = new UriTemplate("http://aaa@[2607:f0d0:1002:51::4]:{varPort}/"); + assertFalse(uriTemplate.opaque()); + assertEquals("aaa", uriTemplate.userInfo()); + assertEquals("[2607:f0d0:1002:51::4]", uriTemplate.host()); + assertEquals("{varPort}", uriTemplate.port()); + assertEquals("/", uriTemplate.path()); + assertNull(uriTemplate.query()); + } + + @Test public void hierarchicalIP4() { + UriTemplate uriTemplate = new UriTemplate("http://{varIP4}:{varPort}/"); + assertFalse(uriTemplate.opaque()); + assertNull(uriTemplate.userInfo()); + assertEquals("{varIP4}", uriTemplate.host()); + assertEquals("{varPort}", uriTemplate.port()); + assertEquals("/", uriTemplate.path()); + assertNull(uriTemplate.query()); + } + + @Test public void hierarchicalFragment() { + UriTemplate uriTemplate = new UriTemplate("{schema}://{user}@{varIP4}#xyz"); + assertFalse(uriTemplate.opaque()); + assertEquals("{schema}", uriTemplate.scheme()); + assertEquals("{user}", uriTemplate.userInfo()); + assertEquals("{varIP4}", uriTemplate.host()); + assertNull(uriTemplate.port()); + assertEquals("",uriTemplate.path()); + assertNull(uriTemplate.query()); + assertEquals("xyz", uriTemplate.fragment()); + } + + @Test public void templateVaribaleEqualsAnsHash() { + UriTemplate uriTemplate = new UriTemplate("{schema:\\d{2}}://@/{schema:\\d{2}}"); + TemplateVariable var1 = uriTemplate.variables().get(0); + TemplateVariable var2 = uriTemplate.variables().get(1); + assertEquals(var1, var1); + assertEquals(var1, var2); + assertEquals(var1.hashCode(), var2.hashCode()); + } + + @Test public void templateVaribaleNotEquals() { + UriTemplate uriTemplate = new UriTemplate("{schema:\\d{2}}://@/{path}"); + TemplateVariable var1 = uriTemplate.variables().get(0); + TemplateVariable var2 = uriTemplate.variables().get(1); + assertNotEquals(var1, 10); + assertNotEquals(var1, var2); + } +}