diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/PathPattern.java b/core-common/src/main/java/org/glassfish/jersey/uri/PathPattern.java index 518a88a29d..2915e82f8e 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/PathPattern.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/PathPattern.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -64,11 +64,11 @@ public final class PathPattern extends PatternWithGroups { * Path pattern matching the end of a URI path. Can be either empty {@code ""} * or contain a trailing slash {@code "/"}. */ - public static final PathPattern END_OF_PATH_PATTERN = new PathPattern("", PathPattern.RightHandPath.capturingZeroSegments); + public static final PathPattern END_OF_PATH_PATTERN = new PathPattern("", PathPattern.RightHandPath.capturingZeroSegments); /** * Path pattern matching the any URI path. */ - public static final PathPattern OPEN_ROOT_PATH_PATTERN = new PathPattern("", RightHandPath.capturingZeroOrMoreSegments); + public static final PathPattern OPEN_ROOT_PATH_PATTERN = new PathPattern("", RightHandPath.capturingZeroOrMoreSegments); /** * Path pattern comparator that defers to {@link UriTemplate#COMPARATOR comparing * the templates} associated with the patterns. @@ -119,6 +119,7 @@ private String getRegex() { public static PathPattern asClosed(PathPattern pattern) { return new PathPattern(pattern.getTemplate().getTemplate(), RightHandPath.capturingZeroSegments); } + // private final UriTemplate template; @@ -132,7 +133,6 @@ private PathPattern() { * {@link RightHandPath#capturingZeroOrMoreSegments}. * * @param template the path template. - * * @see #PathPattern(String, PathPattern.RightHandPath) */ public PathPattern(String template) { @@ -144,12 +144,11 @@ public PathPattern(String template) { * {@link RightHandPath#capturingZeroOrMoreSegments}. * * @param template the path template - * * @see #PathPattern(PathTemplate, PathPattern.RightHandPath) */ public PathPattern(PathTemplate template) { super(postfixWithCapturingGroup(template.getPattern().getRegex()), - addIndexForRightHandPathCapturingGroup(template.getPattern().getGroupIndexes())); + addIndexForRightHandPathCapturingGroup(template.getNumberOfRegexGroups(), template.getPattern().getGroupIndexes())); this.template = template; } @@ -158,7 +157,7 @@ public PathPattern(PathTemplate template) { * Create a path pattern and post fix with a right hand path pattern. * * @param template the path template. - * @param rhpp the right hand path pattern postfix. + * @param rhpp the right hand path pattern postfix. */ public PathPattern(String template, RightHandPath rhpp) { this(new PathTemplate(template), rhpp); @@ -168,11 +167,11 @@ public PathPattern(String template, RightHandPath rhpp) { * Create a path pattern and post fix with a right hand path pattern. * * @param template the path template. - * @param rhpp the right hand path pattern postfix. + * @param rhpp the right hand path pattern postfix. */ public PathPattern(PathTemplate template, RightHandPath rhpp) { super(postfixWithCapturingGroup(template.getPattern().getRegex(), rhpp), - addIndexForRightHandPathCapturingGroup(template.getPattern().getGroupIndexes())); + addIndexForRightHandPathCapturingGroup(template.getNumberOfRegexGroups(), template.getPattern().getGroupIndexes())); this.template = template; } @@ -193,7 +192,7 @@ private static String postfixWithCapturingGroup(String regex, RightHandPath rhpp return regex + rhpp.getRegex(); } - private static int[] addIndexForRightHandPathCapturingGroup(int[] indexes) { + private static int[] addIndexForRightHandPathCapturingGroup(int numberOfGroups, int[] indexes) { if (indexes.length == 0) { return indexes; } @@ -201,7 +200,7 @@ private static int[] addIndexForRightHandPathCapturingGroup(int[] indexes) { int[] cgIndexes = new int[indexes.length + 1]; System.arraycopy(indexes, 0, cgIndexes, 0, indexes.length); - cgIndexes[indexes.length] = cgIndexes[indexes.length - 1] + 1; + cgIndexes[indexes.length] = numberOfGroups + 1; return cgIndexes; } } diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/PatternWithGroups.java b/core-common/src/main/java/org/glassfish/jersey/uri/PatternWithGroups.java index 9f6fc2aea2..80d2f00d88 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/PatternWithGroups.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/PatternWithGroups.java @@ -52,6 +52,7 @@ * the expression. * * @author Paul Sandoz + * @author Gerard Davison (gerard.davison at oracle.com) */ public class PatternWithGroups { @@ -96,7 +97,8 @@ public PatternWithGroups(final String regex) throws PatternSyntaxException { /** * Construct a new pattern. * - * @param regex the regular expression. If the expression is {@code null} or an empty string then the pattern will only + * @param regex the regular expression. If the expression is {@code null} or an empty string then the pattern will + * only * match * a {@code null} or empty string. * @param groupIndexes the array of group indexes to capturing groups. @@ -260,7 +262,7 @@ public String group(final int group) { @Override public int groupCount() { - return groupIndexes.length - 1; + return groupIndexes.length; } } @@ -323,7 +325,7 @@ public final boolean match(final CharSequence cs, final List groupValues groupValues.clear(); if (groupIndexes.length > 0) { - for (int i = 0; i < groupIndexes.length - 1; i++) { + for (int i = 0; i < groupIndexes.length; i++) { groupValues.add(m.group(groupIndexes[i])); } } else { @@ -354,7 +356,7 @@ public final boolean match(final CharSequence cs, final List groupValues * @throws IllegalArgumentException if group values is {@code null}. */ public final boolean match(final CharSequence cs, final List groupNames, final Map groupValues) throws IllegalArgumentException { + String> groupValues) throws IllegalArgumentException { if (groupValues == null) { throw new IllegalArgumentException(); } @@ -374,6 +376,7 @@ public final boolean match(final CharSequence cs, final List groupNames, // Assign the matched group values to group names groupValues.clear(); + for (int i = 0; i < groupNames.size(); i++) { String name = groupNames.get(i); String currentValue = m.group((groupIndexes.length > 0) ? groupIndexes[i] : i + 1); diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java b/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java index 9fea004097..45299374e4 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java @@ -60,9 +60,10 @@ * * @author Paul Sandoz * @author Martin Matula (martin.matula at oracle.com) + * @author Gerard Davison (gerard.davison at oracle.com) */ public class UriTemplate { - private static String[] EMPTY_VALUES = new String[0]; + private static final String[] EMPTY_VALUES = new String[0]; /** * Order the templates according to JAX-RS specification. @@ -131,12 +132,32 @@ public int compare(UriTemplate o1, UriTemplate o2) { return o2.pattern.getRegex().compareTo(o1.pattern.getRegex()); } }; + + /** + * A strategy interface for processing parameters, should be replaced with + * a JDK 8 one day in the future. + */ + private static interface TemplateValueStrategy { + /** + * Get a value for a given template variable. + * + * @param templateVariable template variable. + * @param matchedGroup matched group string for a given template variable. + * @return template value. + * + * @throws java.lang.IllegalArgumentException in case no value has been found and the strategy + * does not support {@code null} values. + */ + public String valueFor(String templateVariable, String matchedGroup); + } + /** * The regular expression for matching URI templates and names. */ - private static final Pattern TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{(\\w[-\\w\\.]*)\\}"); + private static final Pattern TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{([\\w\\?;][-\\w\\.,]*)\\}"); + /** - * The empty URI template that matches the null or empty URI path. + * The empty URI template that matches the {@code null} or empty URI path. */ public static final UriTemplate EMPTY = new UriTemplate(); /** @@ -165,6 +186,12 @@ public int compare(UriTemplate o1, UriTemplate o2) { * variables. */ private final int numOfExplicitRegexes; + + /** + * The number of regular expression groups in this pattern. + */ + private final int numOfRegexGroups; + /** * The number of characters in the regular expression not resulting * from conversion of template variables. @@ -179,7 +206,7 @@ private UriTemplate() { this.pattern = PatternWithGroups.EMPTY; this.endsWithSlash = false; this.templateVariables = Collections.emptyList(); - this.numOfExplicitRegexes = this.numOfCharacters = 0; + this.numOfExplicitRegexes = this.numOfCharacters = this.numOfRegexGroups = 0; } /** @@ -200,8 +227,7 @@ private UriTemplate() { * an empty string. */ @SuppressWarnings("DuplicateThrows") - public UriTemplate(String template) throws - PatternSyntaxException, IllegalArgumentException { + public UriTemplate(String template) throws PatternSyntaxException, IllegalArgumentException { this(new UriTemplateParser(template)); } @@ -222,8 +248,7 @@ public UriTemplate(String template) throws * an empty string. */ @SuppressWarnings("DuplicateThrows") - protected UriTemplate(UriTemplateParser templateParser) throws - PatternSyntaxException, IllegalArgumentException { + protected UriTemplate(UriTemplateParser templateParser) throws PatternSyntaxException, IllegalArgumentException { this.template = templateParser.getTemplate(); this.normalizedTemplate = templateParser.getNormalizedTemplate(); @@ -232,6 +257,8 @@ protected UriTemplate(UriTemplateParser templateParser) throws this.numOfExplicitRegexes = templateParser.getNumberOfExplicitRegexes(); + this.numOfRegexGroups = templateParser.getNumberOfRegexGroups(); + this.numOfCharacters = templateParser.getNumberOfLiteralCharacters(); this.endsWithSlash = template.charAt(template.length() - 1) == '/'; @@ -256,6 +283,7 @@ private static PatternWithGroups initUriPattern(UriTemplateParser templateParser * @param baseUri base URI to be used for resolution. * @param refUri reference URI string to be resolved against the base URI. * @return resolved URI. + * * @throws IllegalArgumentException If the given string violates the URI specification RFC. */ public static URI resolve(final URI baseUri, String refUri) { @@ -306,6 +334,7 @@ public static URI resolve(final URI baseUri, URI refUri) { * * @param uri the original URI string. * @return the URI with dot and dot-dot segments resolved. + * * @throws IllegalArgumentException If the given string violates the URI specification RFC. * @see java.net.URI#normalize() */ @@ -325,6 +354,7 @@ public static URI normalize(final String uri) { * * @param uri the original URI. * @return the URI with dot and dot-dot segments resolved. + * * @see java.net.URI#normalize() */ public static URI normalize(final URI uri) { @@ -340,7 +370,7 @@ public static URI normalize(final URI uri) { final Deque resolvedSegments = new ArrayDeque(segments.length); for (final String segment : segments) { - if ((segment.length() == 0) || (".".equals(segment))) { + if (segment.isEmpty() || ".".equals(segment)) { // skip } else if ("..".equals(segment)) { resolvedSegments.pollLast(); @@ -428,8 +458,7 @@ public final List getTemplateVariables() { * template. * * @param name name The template variable. - * @return {@code true} if the template variable is a member of the template, otherwise - * false. + * @return {@code true} if the template variable is a member of the template, otherwise {@code false}. */ @SuppressWarnings("UnusedDeclaration") public final boolean isTemplateVariablePresent(String name) { @@ -443,14 +472,23 @@ public final boolean isTemplateVariablePresent(String name) { } /** - * Get the number of explicit regexes declared in template variables. + * Get the number of explicit regular expressions declared in the template variables. * - * @return the number of explicit regexes. + * @return the number of explicit regular expressions in the template variables. */ public final int getNumberOfExplicitRegexes() { return numOfExplicitRegexes; } + /** + * Get the number of regular expression groups + * + * @return the number of regular expressions groups + */ + public final int getNumberOfRegexGroups() { + return numOfRegexGroups; + } + /** * Get the number of characters in the regular expression not resulting * from conversion of template variables. @@ -483,6 +521,7 @@ public final int getNumberOfTemplateVariables() { * and template values (as values). The map is cleared before any * entries are put. * @return true if the URI matches the template, otherwise false. + * * @throws IllegalArgumentException if the uri or * templateVariableToValue is null. */ @@ -507,6 +546,7 @@ public final boolean match(CharSequence uri, Map templateVariabl * capturing groups is matching is successful. The values are stored * in the same order as the pattern's capturing groups. * @return true if the URI matches the template, otherwise false. + * * @throws IllegalArgumentException if the uri or * templateVariableToValue is null. */ @@ -529,32 +569,26 @@ public final boolean match(CharSequence uri, List groupValues) throws * @param values the map of template variables to template values. * @return the URI. */ - public final String createURI(Map values) { - StringBuilder b = new StringBuilder(); - // Find all template variables - Matcher m = TEMPLATE_NAMES_PATTERN.matcher(normalizedTemplate); - int i = 0; - while (m.find()) { - b.append(normalizedTemplate, i, m.start()); - String tValue = values.get(m.group(1)); - if (tValue != null) { - b.append(tValue); + public final String createURI(final Map values) { + final StringBuilder sb = new StringBuilder(); + resolveTemplate(normalizedTemplate, sb, new TemplateValueStrategy() { + @Override + public String valueFor(String templateVariable, String matchedGroup) { + return values.get(templateVariable); } - i = m.end(); - } - b.append(normalizedTemplate, i, normalizedTemplate.length()); - return b.toString(); + }); + return sb.toString(); } /** * Create a URI by substituting any template variables * for corresponding template values. *

- * A URI template varibale without a value will be substituted by the + * A URI template variable without a value will be substituted by the * empty string. * * @param values the array of template values. The values will be - * substituted in order of occurence of unique template variables. + * substituted in order of occurrence of unique template variables. * @return the URI. */ public final String createURI(String... values) { @@ -569,41 +603,116 @@ public final String createURI(String... values) { * empty string. * * @param values the array of template values. The values will be - * substituted in order of occurence of unique template variables. - * @param offset the offset into the array - * @param length the length of the array + * substituted in order of occurrence of unique template variables. + * @param offset the offset into the template value array. + * @param length the length of the template value array. * @return the URI. */ - public final String createURI(String[] values, int offset, int length) { - Map mapValues = new HashMap(); - StringBuilder b = new StringBuilder(); + public final String createURI(final String[] values, final int offset, final int length) { + + TemplateValueStrategy ns = new TemplateValueStrategy() { + private final int lengthPlusOffset = length + offset; + private int v = offset; + private final Map mapValues = new HashMap(); + + @Override + public String valueFor(String templateVariable, String matchedGroup) { + // Check if a template variable has already occurred + // If so use the value to ensure that two or more declarations of + // a template variable have the same value + String tValue = mapValues.get(templateVariable); + if (tValue == null) { + if (v < lengthPlusOffset) { + tValue = values[v++]; + if (tValue != null) { + mapValues.put(templateVariable, tValue); + } + } + } + + return tValue; + } + }; + + final StringBuilder sb = new StringBuilder(); + resolveTemplate(normalizedTemplate, sb, ns); + return sb.toString(); + } + + /** + * Build a URI based on the parameters provided by the variable name strategy. + * + * @param normalizedTemplate normalized URI template. A normalized template is a template without any explicit regular + * expressions. + * @param builder URI string builder to be used. + * @param valueStrategy The template value producer strategy to use. + */ + private static void resolveTemplate( + String normalizedTemplate, + StringBuilder builder, + TemplateValueStrategy valueStrategy) { // Find all template variables Matcher m = TEMPLATE_NAMES_PATTERN.matcher(normalizedTemplate); - int v = offset; - length += offset; + int i = 0; while (m.find()) { - b.append(normalizedTemplate, i, m.start()); - String tVariable = m.group(1); - // Check if a template variable has already occurred - // If so use the value to ensure that two or more declarations of - // a template variable have the same value - String tValue = mapValues.get(tVariable); - if (tValue != null) { - b.append(tValue); - } else { - if (v < length) { - tValue = values[v++]; - if (tValue != null) { - mapValues.put(tVariable, tValue); - b.append(tValue); + builder.append(normalizedTemplate, i, m.start()); + String variableName = m.group(1); + // TODO matrix + char firstChar = variableName.charAt(0); + if (firstChar == '?' || firstChar == ';') { + final char prefix; + final char separator; + final String emptyValueAssignment; + if (firstChar == '?') { + // query + prefix = '?'; + separator = '&'; + emptyValueAssignment = "="; + } else { + // matrix + prefix = ';'; + separator = ';'; + emptyValueAssignment = ""; + } + + int index = builder.length(); + String[] variables = variableName.substring(1).split(", ?"); + for (String variable : variables) { + try { + String value = valueStrategy.valueFor(variable, m.group()); + if (value != null) { + if (index != builder.length()) { + builder.append(separator); + } + + builder.append(variable); + if (value.isEmpty()) { + builder.append(emptyValueAssignment); + } else { + builder.append('='); + builder.append(value); + } + } + } catch (IllegalArgumentException ex) { + // no value found => ignore the variable } } + + if (index != builder.length() && (index == 0 || builder.charAt(index - 1) != prefix)) { + builder.insert(index, prefix); + } + } else { + String value = valueStrategy.valueFor(variableName, m.group()); + + if (value != null) { + builder.append(value); + } } + i = m.end(); } - b.append(normalizedTemplate, i, normalizedTemplate.length()); - return b.toString(); + builder.append(normalizedTemplate, i, normalizedTemplate.length()); } @Override @@ -612,7 +721,7 @@ public final String toString() { } /** - * Hashcode is calculated from String of the regular expression + * Hash code is calculated from String of the regular expression * generated from the template. * * @return the hash code. @@ -807,7 +916,7 @@ private static String createURIWithStringValues( int offset = 0; if (scheme != null) { - offset = createURIComponent(UriComponent.Type.SCHEME, scheme, values, + offset = createUriComponent(UriComponent.Type.SCHEME, scheme, values, offset, false, mapValues, sb); sb.append(':'); } @@ -816,26 +925,26 @@ private static String createURIWithStringValues( sb.append("//"); if (notEmpty(userInfo)) { - offset = createURIComponent(UriComponent.Type.USER_INFO, userInfo, values, + offset = createUriComponent(UriComponent.Type.USER_INFO, userInfo, values, offset, encode, mapValues, sb); sb.append('@'); } if (notEmpty(host)) { // TODO check IPv6 address - offset = createURIComponent(UriComponent.Type.HOST, host, values, + offset = createUriComponent(UriComponent.Type.HOST, host, values, offset, encode, mapValues, sb); } if (notEmpty(port)) { sb.append(':'); - offset = createURIComponent(UriComponent.Type.PORT, port, values, + offset = createUriComponent(UriComponent.Type.PORT, port, values, offset, false, mapValues, sb); } } else if (notEmpty(authority)) { sb.append("//"); - offset = createURIComponent(UriComponent.Type.AUTHORITY, authority, values, + offset = createUriComponent(UriComponent.Type.AUTHORITY, authority, values, offset, encode, mapValues, sb); } @@ -849,19 +958,19 @@ private static String createURIWithStringValues( // path template values are treated as path segments unless encodeSlashInPath is false. UriComponent.Type t = (encodeSlashInPath) ? UriComponent.Type.PATH_SEGMENT : UriComponent.Type.PATH; - offset = createURIComponent(t, path, values, + offset = createUriComponent(t, path, values, offset, encode, mapValues, sb); } if (notEmpty(query)) { sb.append('?'); - offset = createURIComponent(UriComponent.Type.QUERY_PARAM, query, values, + offset = createUriComponent(UriComponent.Type.QUERY_PARAM, query, values, offset, encode, mapValues, sb); } if (notEmpty(fragment)) { sb.append('#'); - createURIComponent(UriComponent.Type.FRAGMENT, fragment, values, + createUriComponent(UriComponent.Type.FRAGMENT, fragment, values, offset, encode, mapValues, sb); } } @@ -873,50 +982,51 @@ private static boolean notEmpty(String string) { } @SuppressWarnings("unchecked") - private static int createURIComponent(UriComponent.Type t, + private static int createUriComponent(final UriComponent.Type componentType, String template, - final String[] values, final int offset, + final String[] values, + final int valueOffset, final boolean encode, final Map _mapValues, final StringBuilder b) { - Map mapValues = (Map) _mapValues; + final Map mapValues = (Map) _mapValues; if (template.indexOf('{') == -1) { b.append(template); - return offset; + return valueOffset; } // Find all template variables template = new UriTemplateParser(template).getNormalizedTemplate(); - final Matcher m = TEMPLATE_NAMES_PATTERN.matcher(template); - int v = offset; - int i = 0; - while (m.find()) { - b.append(template, i, m.start()); - final String tVariable = m.group(1); - // Check if a template variable has already occurred - // If so use the value to ensure that two or more declarations of - // a template variable have the same value - Object tValue = mapValues.get(tVariable); - if (tValue == null && v < values.length) { - tValue = values[v++]; - } - if (tValue != null) { - mapValues.put(tVariable, tValue); + + + class ValuesFromArrayStrategy implements TemplateValueStrategy { + private int offset = valueOffset; + + @Override + public String valueFor(String templateVariable, String matchedGroup) { + + Object value = mapValues.get(templateVariable); + if (value == null && offset < values.length) { + value = values[offset++]; + mapValues.put(templateVariable, value); + } + if (value == null) { + throw new IllegalArgumentException( + String.format("The template variable '%s' has no value", templateVariable)); + } if (encode) { - tValue = UriComponent.encode(tValue.toString(), t); + return UriComponent.encode(value.toString(), componentType); } else { - tValue = UriComponent.contextualEncode(tValue.toString(), t); + return UriComponent.contextualEncode(value.toString(), componentType); } - b.append(tValue); - } else { - throw templateVariableHasNoValue(tVariable); } - i = m.end(); } - b.append(template, i, template.length()); - return v; + ValuesFromArrayStrategy cs = new ValuesFromArrayStrategy(); + resolveTemplate(template, b, cs); + + return cs.offset; } @@ -929,10 +1039,11 @@ private static int createURIComponent(UriComponent.Type t, * @param encode True if template values from {@code _mapValues} should be percent encoded. * @param _mapValues Map with template variables as keys and template values as values. None of them should be null. * @return String with resolved template variables. + * * @throws IllegalArgumentException when {@code _mapValues} value is null. */ @SuppressWarnings("unchecked") - public static String resolveTemplateValues(UriComponent.Type type, + public static String resolveTemplateValues(final UriComponent.Type type, String template, final boolean encode, final Map _mapValues) { @@ -941,42 +1052,38 @@ public static String resolveTemplateValues(UriComponent.Type type, return template; } - Map mapValues = (Map) _mapValues; - StringBuilder sb = new StringBuilder(); + final Map mapValues = (Map) _mapValues; // Find all template variables template = new UriTemplateParser(template).getNormalizedTemplate(); - final Matcher m = TEMPLATE_NAMES_PATTERN.matcher(template); - int i = 0; - while (m.find()) { - sb.append(template, i, m.start()); - final String tVariable = m.group(1); - Object tValue = mapValues.get(tVariable); + StringBuilder sb = new StringBuilder(); + resolveTemplate(template, sb, new TemplateValueStrategy() { + @Override + public String valueFor(String templateVariable, String matchedGroup) { - if (tValue != null) { - if (encode) { - tValue = UriComponent.encode(tValue.toString(), type); + Object value = mapValues.get(templateVariable); + + if (value != null) { + if (encode) { + value = UriComponent.encode(value.toString(), type); + } else { + value = UriComponent.contextualEncode(value.toString(), type); + } + return value.toString(); } else { - tValue = UriComponent.contextualEncode(tValue.toString(), type); - } - sb.append(tValue); - } else { - if (mapValues.containsKey(tVariable)) { - throw new IllegalArgumentException("The value associated of the template value map for key + " + tVariable - + " is null."); - } + if (mapValues.containsKey(templateVariable)) { + throw new IllegalArgumentException( + String.format("The value associated of the template value map for key '%s' is 'null'.", + templateVariable) + ); + } - sb.append(m.group()); + return matchedGroup; + } } - i = m.end(); - } - sb.append(template, i, template.length()); - return sb.toString(); - } + }); - private static IllegalArgumentException templateVariableHasNoValue(String tVariable) { - return new IllegalArgumentException("The template variable, " - + tVariable + ", has no value"); + return sb.toString(); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java index e53d48b687..ae19e24f6e 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java @@ -58,6 +58,7 @@ * A URI template parser that parses JAX-RS specific URI templates. * * @author Paul Sandoz + * @author Gerard Davison (gerard.davison at oracle.com) */ public class UriTemplateParser { /* package */ static final int[] EMPTY_INT_ARRAY = new int[0]; @@ -89,6 +90,8 @@ private static Set initReserved() { private final List groupCounts = new ArrayList(); private final Map nameToPattern = new HashMap(); private int numOfExplicitRegexes; + private int skipGroup; + private int literalCharacters; /** @@ -96,11 +99,11 @@ private static Set initReserved() { * * @param template the template. * @throws IllegalArgumentException if the template is null, an empty string - * or does not conform to a JAX-RS URI template. + * or does not conform to a JAX-RS URI template. */ public UriTemplateParser(final String template) throws IllegalArgumentException { - if (template == null || template.length() == 0) { - throw new IllegalArgumentException(); + if (template == null || template.isEmpty()) { + throw new IllegalArgumentException("Template is null or has zero length"); } this.template = template; @@ -110,7 +113,8 @@ public UriTemplateParser(final String template) throws IllegalArgumentException } catch (PatternSyntaxException ex) { throw new IllegalArgumentException("Invalid syntax for the template expression '" + regex + "'", - ex); + ex + ); } } @@ -185,11 +189,12 @@ public final int[] getGroupIndexes() { return EMPTY_INT_ARRAY; } - int[] indexes = new int[names.size() + 1]; - indexes[0] = 1; + int[] indexes = new int[names.size()]; + indexes[0] = 0 + groupCounts.get(0); for (int i = 1; i < indexes.length; i++) { - indexes[i] = indexes[i - 1] + groupCounts.get(i - 1); + indexes[i] = indexes[i - 1] + groupCounts.get(i); } + return indexes; } @@ -202,6 +207,22 @@ public final int getNumberOfExplicitRegexes() { return numOfExplicitRegexes; } + /** + * Get the number of regular expression groups + * + * @return the number of regular expressions groups + * + * @since 2.9 + */ + public final int getNumberOfRegexGroups() { + if (groupCounts.isEmpty()) { + return 0; + } else { + int[] groupIndex = getGroupIndexes(); + return groupIndex[groupIndex.length - 1] + skipGroup; + } + } + /** * Get the number of literal characters. * @@ -227,7 +248,7 @@ private void parse(final CharacterIterator ci) { char c = ci.next(); if (c == '{') { processLiteralCharacters(); - parseName(ci); + skipGroup = parseName(ci, skipGroup); } else { literalCharactersBuffer.append(c); } @@ -278,21 +299,28 @@ private static String[] initHexToUpperCaseRegex() { for (char c = 'a'; c <= 'f'; c++) { // initialize table values: table[a] = ([aA]) ... - table[c] = new StringBuffer().append("[").append(c).append((char) (c - 'a' + 'A')).append("]").toString(); + table[c] = "[" + c + (char) (c - 'a' + 'A') + "]"; } for (char c = 'A'; c <= 'F'; c++) { // initialize table values: table[A] = ([aA]) ... - table[c] = new StringBuffer().append("[").append((char) (c - 'A' + 'a')).append(c).append("]").toString(); + table[c] = "[" + (char) (c - 'A' + 'a') + c + "]"; } return table; } - - private void parseName(final CharacterIterator ci) { + private int parseName(final CharacterIterator ci, int skipGroup) { char c = consumeWhiteSpace(ci); + char paramType = 'p'; // Normal path param unless otherwise stated StringBuilder nameBuffer = new StringBuilder(); + + // Look for query or matrix types + if (c == '?' || c == ';') { + paramType = c; + c = ci.next(); + } + if (Character.isLetterOrDigit(c) || c == '_') { // Template name character nameBuffer.append(c); @@ -308,7 +336,10 @@ private void parseName(final CharacterIterator ci) { if (Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == '.') { // Template name character nameBuffer.append(c); - } else if (c == ':') { + } else if (c == ',' && paramType != 'p') { + // separator allowed for non-path parameter names + nameBuffer.append(c); + } else if (c == ':' && paramType == 'p') { nameRegexString = parseRegex(ci); break; } else if (c == '}') { @@ -323,47 +354,97 @@ private void parseName(final CharacterIterator ci) { break; } else { // Error - throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_AFTER_NAME(c, - ci.pos(), template)); + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_AFTER_NAME(c, ci.pos(), template)); } } else { - throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_PART_OF_NAME(c, - ci.pos(), template)); + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_PART_OF_NAME(c, ci.pos(), template)); } } - String name = nameBuffer.toString(); - names.add(name); + String name = nameBuffer.toString(); + Pattern namePattern; try { - if (nameRegexString.length() > 0) { - numOfExplicitRegexes++; - } - Pattern namePattern = (nameRegexString.length() == 0) - ? TEMPLATE_VALUE_PATTERN : Pattern.compile(nameRegexString); - if (nameToPattern.containsKey(name)) { - if (!nameToPattern.get(name).equals(namePattern)) { - throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_NAME_MORE_THAN_ONCE(name, - template)); + if (paramType == '?' || paramType == ';') { + String[] subNames = name.split(",\\s?"); + + // Build up the regex for each of these properties + StringBuilder regexBuilder = new StringBuilder(paramType == '?' ? "\\?" : ";"); + String separator = paramType == '?' ? "\\&" : ";/\\?"; + + // Start a group because each parameter could repeat +// names.add("__" + (paramType == '?' ? "query" : "matrix")); + + boolean first = true; + + regexBuilder.append("("); + for (String subName : subNames) { + regexBuilder.append("(&?"); + regexBuilder.append(subName); + regexBuilder.append("(=([^"); + regexBuilder.append(separator); + regexBuilder.append("]*))?"); + regexBuilder.append(")"); + if (!first) { + regexBuilder.append("|"); + } + + names.add(subName); + groupCounts.add( + first ? 5 : 3); + first = false; } + +// groupCounts.add(1); + skipGroup = 1; + + // Knock of last bar + regexBuilder.append(")*"); + + namePattern = Pattern.compile(regexBuilder.toString()); + + // Make sure we display something useful + name = paramType + name; } else { - nameToPattern.put(name, namePattern); - } + names.add(name); + // groupCounts.add(1 + skipGroup); - // Determine group count of pattern - Matcher m = namePattern.matcher(""); - int g = m.groupCount(); - groupCounts.add(g + 1); + if (!nameRegexString.isEmpty()) { + numOfExplicitRegexes++; + } + namePattern = (nameRegexString.isEmpty()) + ? TEMPLATE_VALUE_PATTERN : Pattern.compile(nameRegexString); + if (nameToPattern.containsKey(name)) { + if (!nameToPattern.get(name).equals(namePattern)) { + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_NAME_MORE_THAN_ONCE(name, template)); + } + } else { + nameToPattern.put(name, namePattern); + } + + // Determine group count of pattern + Matcher m = namePattern.matcher(""); + int g = m.groupCount(); + groupCounts.add(1 + skipGroup); + skipGroup = g; + } regex.append('('). append(namePattern). append(')'); + normalizedTemplate.append('{'). append(name). append('}'); } catch (PatternSyntaxException ex) { - throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_INVALID_SYNTAX(nameRegexString, name, - template), ex); + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_INVALID_SYNTAX(nameRegexString, name, template), ex); } + + // Tell the next time through the loop how many to skip + return skipGroup; } private String parseRegex(final CharacterIterator ci) { diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java index 2b35cc8f0b..e3da83b6e6 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java @@ -51,6 +51,7 @@ * Tests {@link PathTemplate}. * * @author Marek Potociar (marek.potociar at oracle.com) + * @author Gerard Davison (gerard.davison at oracle.com) */ public class PathPatternTest { @@ -125,4 +126,19 @@ public void testSetsAndGetsUriTemplate() throws Exception { pattern.getTemplate() ); } + + @Test + public void testLastElementOfMatchIsRestOfPath() throws Exception { + PathPattern path = new PathPattern("{a: (\\d)(\\d*)}-{b: (\\d)(\\d*)}-{c: (\\d)(\\d*)}"); + + + MatchResult m = path.match("/123-456-789/d"); + String value = m.group(m.groupCount()); + + assertEquals( + "Last value should match all of the trailing part", + "/d", + value + ); + } } diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java index 84f1ae76b5..ffda1fba6a 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java @@ -41,6 +41,7 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -58,7 +59,8 @@ /** * Taken from Jersey 1: jersey-tests: com.sun.jersey.impl.uri.UriTemplateTest * - * @author Paul Sandoz + * @author Paul.Sandoz + * @author Gerard Davison (gerard.davison at oracle.com) */ public class UriTemplateTest { @@ -273,16 +275,10 @@ void _testMatching(String template, String uri, String... values) { UriTemplate t = new UriTemplate(template); Map m = new HashMap(); - System.out.println("TEMPLATE: " + template); - System.out.println("REGEX: " + t.getPattern().getRegex()); - System.out.println("TEMPLATE NAMES: " + t.getTemplateVariables()); - boolean isMatch = t.match(uri, m); assertTrue(isMatch); assertEquals(values.length, t.getTemplateVariables().size()); - System.out.println("MAP: " + m); - Iterator names = t.getTemplateVariables().iterator(); for (String value : values) { String mapValue = m.get(names.next()); @@ -295,8 +291,6 @@ void _testMatching(String template, String uri, String... values) { assertTrue(isMatch); assertEquals(values.length, matchedValues.size()); - System.out.println("LIST: " + matchedValues); - for (int i = 0; i < values.length; i++) { assertEquals(values[i], matchedValues.get(i)); } @@ -311,9 +305,9 @@ void _testMatching(String template, String uri, String... values) { assertEquals(uri.length(), mr.end()); assertEquals(0, mr.start(0)); assertEquals(uri.length(), mr.end(0)); - for (int i = 1; i <= mr.groupCount(); i++) { - assertEquals(values[i - 1], mr.group(i)); - assertEquals(values[i - 1], uri.substring(mr.start(i), mr.end(i))); + for (int i = 0; i < mr.groupCount(); i++) { + assertEquals(values[i], mr.group(i + 1)); + assertEquals(values[i], uri.substring(mr.start(i + 1), mr.end(i + 1))); } } @@ -404,31 +398,31 @@ public void testGroupIndexes() throws Exception { assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[0])); template = new UriTemplate("/{a}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1})); template = new UriTemplate("/{a}/b"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1})); template = new UriTemplate("/{a}/{b}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2, 3})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2})); template = new UriTemplate("/{a}/{b}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2, 3})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2})); template = new UriTemplate("/{a}/b/{c}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2, 3})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 2})); template = new UriTemplate("/{a: (abc)+}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1})); template = new UriTemplate("/{a: (abc)+}/b"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1})); template = new UriTemplate("/{a: (abc)+}/{b}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3, 4})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3})); template = new UriTemplate("/{a: (abc)+}/b/{c}"); - assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3, 4})); + assertThat(template.getPattern().getGroupIndexes(), equalTo(new int[] {1, 3})); } void _testSubstitutionArray(String template, String uri, String... values) { @@ -496,4 +490,293 @@ private void validateNormalize(String path, String expected) throws Exception { assertEquals(expected, result.toString()); } + @Test + public void testSingleQueryParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{?query}"); + + Map result = new HashMap(); + tmpl.match("/test?query=x", result); + + assertEquals( + "incorrect size for match string", + 1, + result.size() + ); + + assertEquals( + "query parameter is not matched", + "x", + result.get("query") + ); + } + + @Test + public void testDoubleQueryParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{?query,secondQuery}"); + + List list = new ArrayList(); + tmpl.match("/test?query=x&secondQuery=y", list); + + Map result = new HashMap(); + tmpl.match("/test?query=x&secondQuery=y", result); + + assertEquals( + "incorrect size for match string", + 2, + result.size() + ); + + assertEquals( + "query parameter is not matched", + "x", + result.get("query") + ); + assertEquals( + "query parameter is not matched", + "y", + result.get("secondQuery") + ); + } + + + @Test + public void testSettingQueryParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{?query}"); + + Map values = new HashMap(); + values.put("query", "example"); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is not set", + "/test?query=example", + uri + ); + } + + @Test + public void testSettingTwoQueryParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{?query,other}"); + + Map values = new HashMap(); + values.put("query", "example"); + values.put("other", "otherExample"); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is not set", + "/test?query=example&other=otherExample", + uri + ); + + } + + @Test + public void testNotSettingQueryParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{?query}"); + + Map values = new HashMap(); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is set", + "/test", + uri + ); + + } + + @Test + public void testSettingMatrixParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{;matrix}/other"); + + Map values = new HashMap(); + values.put("matrix", "example"); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is not set", + "/test;matrix=example/other", + uri + ); + + } + + @Test + public void testSettingTwoMatrixParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{;matrix,other}/other"); + + Map values = new HashMap(); + values.put("matrix", "example"); + values.put("other", "otherExample"); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is not set", + "/test;matrix=example;other=otherExample/other", + uri + ); + + } + + @Test + public void testSettingTwoSeperatedMatrixParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{;matrix}/other{;other}"); + + Map values = new HashMap(); + values.put("matrix", "example"); + values.put("other", "otherExample"); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is not set", + "/test;matrix=example/other;other=otherExample", + uri + ); + } + + @Test + public void testNotSettingMatrixParameter() throws Exception { + UriTemplate tmpl = new UriTemplate("/test{;query}/other"); + + Map values = new HashMap(); + + String uri = tmpl.createURI(values); + assertEquals( + "query string is set", + "/test/other", + uri + ); + } + + /* + RFC 6570, section 3.2: + + count := ("one", "two", "three") + dom := ("example", "com") + dub := "me/too" + hello := "Hello World!" + half := "50%" + var := "value" + who := "fred" + base := "http://example.com/home/" + path := "/foo/bar" + list := ("red", "green", "blue") + keys := [("semi",";"),("dot","."),("comma",",")] + v := "6" + x := "1024" + y := "768" + empty := "" + empty_keys := [] + undef := null + */ + private static final List count = Arrays.asList("one", "two", "three"); + private static final List dom = Arrays.asList("example", "com"); + private static final String dub = "me/too"; + private static final String hello = "Hello World!"; + private static final String half = "50%"; + private static final String var = "value"; + private static final String who = "fred"; + private static final String base = "http://example.com/home/"; + private static final String path = "/foo/bar"; + private static final List list = Arrays.asList("red", "green", "blue"); + private static final Map keys = new HashMap() {{ + put("semi", ";"); + put("dot", "."); + put("comma", ","); + }}; + private static final String v = "6"; + private static final String x = "1024"; + private static final String y = "768"; + private static final String empty = ""; + private static final Map emptyKeys = Collections.emptyMap(); + + @Test + public void testRfc6570QueryTemplateExamples() { + /* + RFC 6570, section 3.2.8: + + {?who} ?who=fred + {?half} ?half=50%25 + {?x,y} ?x=1024&y=768 + {?x,y,empty} ?x=1024&y=768&empty= + {?x,y,undef} ?x=1024&y=768 + {?var:3} ?var=val + {?list} ?list=red,green,blue + {?list*} ?list=red&list=green&list=blue + {?keys} ?keys=semi,%3B,dot,.,comma,%2C + {?keys*} ?semi=%3B&dot=.&comma=%2C + */ + assertEncodedQueryTemplateExpansion("?who=fred", "{?who}", who); + assertEncodedQueryTemplateExpansion("?half=50%25", "{?half}", half); + assertEncodedQueryTemplateExpansion("?x=1024&y=768", "{?x,y}", x, y); + assertEncodedQueryTemplateExpansion("?x=1024&y=768&empty=", "{?x,y,empty}", x, y, empty); + assertEncodedQueryTemplateExpansion("?x=1024&y=768", "{?x,y,undef}", x, y); + // TODO assertEncodedQueryTemplateExpansion("?var=val", "{?var:3}", var); + // TODO assertEncodedQueryTemplateExpansion("?list=red,green,blue", "{?list}", list); + // TODO assertEncodedQueryTemplateExpansion("?list=red&list=green&list=blue", "{?list*}", list); + // TODO assertEncodedQueryTemplateExpansion("?keys=semi,%3B,dot,.,comma,%2C", "{?keys}", keys); + // assertEncodedQueryTemplateExpansion("?semi=%3B&dot=.&comma=%2C", "{?keys*}", keys); + } + + private void assertEncodedQueryTemplateExpansion(String expectedExpansion, String queryTemplate, Object... values) { + assertEquals("Unexpected encoded query template expansion result.", + expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false)); + } + + private void assertEncodedQueryTemplateExpansion(String expectedExpansion, String queryTemplate, Map values) { + assertEquals("Unexpected encoded query template expansion result.", + expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false)); + } + + @Test + public void testRfc6570MatrixTemplateExamples() { + /* + RFC 6570, section 3.2.7: + + {;who} ;who=fred + {;half} ;half=50%25 + {;empty} ;empty + {;v,empty,who} ;v=6;empty;who=fred + {;v,bar,who} ;v=6;who=fred + {;x,y} ;x=1024;y=768 + {;x,y,empty} ;x=1024;y=768;empty + {;x,y,undef} ;x=1024;y=768 + {;hello:5} ;hello=Hello + {;list} ;list=red,green,blue + {;list*} ;list=red;list=green;list=blue + {;keys} ;keys=semi,%3B,dot,.,comma,%2C + {;keys*} ;semi=%3B;dot=.;comma=%2C + */ + assertEncodedPathTemplateExpansion(";who=fred", "{;who}", who); + assertEncodedPathTemplateExpansion(";half=50%25", "{;half}", half); + assertEncodedPathTemplateExpansion(";empty", "{;empty}", empty); + assertEncodedPathTemplateExpansion(";v=6;empty;who=fred", "{;v,empty,who}", v, empty, who); + assertEncodedPathTemplateExpansion(";v=6;who=fred", "{;v,bar,who}", new HashMap() {{ + put("v", v); + put("who", who); + }}); + assertEncodedPathTemplateExpansion(";x=1024;y=768", "{;x,y}", x, y); + assertEncodedPathTemplateExpansion(";x=1024;y=768;empty", "{;x,y,empty}", x, y, empty); + assertEncodedPathTemplateExpansion(";x=1024;y=768", "{;x,y,undef}", x, y); + // TODO assertEncodedPathTemplateExpansion(";hello=Hello", "{;hello:5}", hello); + // TODO assertEncodedPathTemplateExpansion(";list=red,green,blue", "{;list}", list); + // TODO assertEncodedPathTemplateExpansion(";list=red;list=green;list=blue", "{;list*}", list); + // TODO assertEncodedPathTemplateExpansion(";keys=semi,%3B,dot,.,comma,%2C", "{;keys}", keys); + // TODO assertEncodedPathTemplateExpansion(";semi=%3B;dot=.;comma=%2C", "{;keys*}", keys); + } + + private void assertEncodedPathTemplateExpansion(String expectedExpansion, String pathTemplate, Object... values) { + assertEquals("Unexpected encoded matrix parameter template expansion result.", + expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false)); + } + + private void assertEncodedPathTemplateExpansion(String expectedExpansion, String pathTemplate, Map values) { + assertEquals("Unexpected encoded matrix parameter template expansion result.", + expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false)); + } } diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java index 77cd9bbd35..868bcc52ce 100644 --- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java @@ -45,13 +45,14 @@ import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; +import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.linking.DeclarativeLinkingFeature; -import org.glassfish.jersey.examples.linking.resources.ItemResource; +import org.glassfish.jersey.examples.linking.resources.ItemsResource; /** * Show link injection in action @@ -62,17 +63,21 @@ public class App { private static final URI BASE_URI = URI.create("http://localhost:8080/"); - public static final String ROOT_PATH = "items/0"; + public static final String ROOT_PATH = "items"; public static void main(String[] args) { try { + Logger.getLogger(HttpHandler.class.getName()).setLevel(Level.ALL); + System.out.println("\"Declarative Linking\" Jersey Example App"); - final ResourceConfig resourceConfig = new ResourceConfig(ItemResource.class); + final ResourceConfig resourceConfig = new ResourceConfig(ItemsResource.class); + resourceConfig.register(DeclarativeLinkingFeature.class); final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig); - System.out.println(String.format("Application started.\nTry out %s%s\nHit enter to stop it...", + + System.out.println(String.format("Application started.\nTry out curl -L %s%s\nHit enter to stop it...", BASE_URI, ROOT_PATH)); System.in.read(); server.shutdownNow(); diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java index fd9c9a8d02..ef1bd983fc 100644 --- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java @@ -64,11 +64,10 @@ public static synchronized ItemsModel getInstance() { private ItemsModel() { items = new ArrayList(); - items.add(new ItemModel("Item 0")); - items.add(new ItemModel("Item 1")); - items.add(new ItemModel("Item 2")); - items.add(new ItemModel("Item 3")); - items.add(new ItemModel("Item 4")); + for(int i=0; i < 100; i++) + { + items.add(new ItemModel("Item " + i)); + } } public boolean hasNext(String currentId) { @@ -94,4 +93,10 @@ public String getPrevId(String id) { private int getIndex(String id) { return Integer.parseInt(id); } + + + public int getSize() + { + return items.size(); + } } diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java index bdb27e1e15..76aba52da7 100644 --- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java @@ -52,7 +52,9 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.glassfish.jersey.examples.linking.model.ItemsModel; /** * JAXB representation of an item @@ -67,15 +69,15 @@ @InjectLink( resource = ItemResource.class, style = Style.ABSOLUTE, - condition = "${resource.next}", - bindings = @Binding(name = "id", value = "${resource.nextId}"), + condition = "${instance.next}", + bindings = @Binding(name = "id", value = "${instance.nextId}"), rel = "next" ), @InjectLink( resource = ItemResource.class, style = Style.ABSOLUTE, - condition = "${resource.prev}", - bindings = @Binding(name = "id", value = "${resource.prevId}"), + condition = "${instance.prev}", + bindings = @Binding(name = "id", value = "${instance.prevId}"), rel = "prev" ) }) @@ -84,10 +86,16 @@ public class ItemRepresentation { @XmlElement private String name; + @XmlTransient + private String id; + @XmlTransient + private ItemsModel itemsModel; + + @InjectLink( resource = ItemResource.class, style = Style.ABSOLUTE, - bindings = @Binding(name = "id", value = "${resource.id}"), + bindings = @Binding(name = "id", value = "${instance.id}"), rel = "self" ) @XmlJavaTypeAdapter(Link.JaxbAdapter.class) @@ -98,28 +106,56 @@ public class ItemRepresentation { @InjectLink( resource = ItemResource.class, style = Style.ABSOLUTE, - condition = "${resource.next}", - bindings = @Binding(name = "id", value = "${resource.nextId}"), + condition = "${instance.next}", + bindings = @Binding(name = "id", value = "${instance.nextId}"), rel = "next" ), @InjectLink( resource = ItemResource.class, style = Style.ABSOLUTE, - condition = "${resource.prev}", - bindings = @Binding(name = "id", value = "${resource.prevId}"), + condition = "${instance.prev}", + bindings = @Binding(name = "id", value = "${instance.prevId}"), rel = "prev" )}) @XmlElement(name="link") @XmlElementWrapper(name = "links") @XmlJavaTypeAdapter(Link.JaxbAdapter.class) List links; + - public ItemRepresentation() { - this.name = ""; + public ItemRepresentation() + { + } - - public ItemRepresentation(String name) { + + public ItemRepresentation(ItemsModel itemsModel, String id, String name) { + this.itemsModel = itemsModel; this.name = name; + this.id = id; + } + + + + public String getId() { + return id; + } + + + public boolean isNext() { + return itemsModel.hasNext(id); + } + + public boolean isPrev() { + return itemsModel.hasPrev(id); + } + + public String getNextId() { + return itemsModel.getNextId(id); + } + + public String getPrevId() { + return itemsModel.getPrevId(id); } + } diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java new file mode 100644 index 0000000000..edc82de181 --- /dev/null +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java @@ -0,0 +1,195 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package org.glassfish.jersey.examples.linking.representation; + +import java.util.ArrayList; +import org.glassfish.jersey.examples.linking.resources.ItemResource; +import org.glassfish.jersey.linking.Binding; +import org.glassfish.jersey.linking.InjectLinks; +import org.glassfish.jersey.linking.InjectLink; +import org.glassfish.jersey.linking.InjectLink.Style; + +import java.util.List; +import javax.ws.rs.core.Link; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.glassfish.jersey.examples.linking.model.ItemsModel; +import org.glassfish.jersey.examples.linking.resources.ItemsResource; + +/** + * JAXB representation of a sublist of items + * + * + * @author Mark Hadley + * @author Gerard Davison (gerard.davison at oracle.com) + */ +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = "items") +@InjectLinks({ + @InjectLink( + resource = ItemsResource.class, + style = Style.ABSOLUTE, + method = "query", + condition = "${instance.offset + instance.limit < instance.modelLimit}", + bindings = { + @Binding(name = "offset", value = "${instance.offset + instance.limit}"), + @Binding(name = "limit", value = "${instance.limit}") + }, + rel = "next" + ), + @InjectLink( + resource = ItemsResource.class, + style = Style.ABSOLUTE, + method = "query", + condition = "${instance.offset - instance.limit >= 0}", + bindings = { + @Binding(name = "offset", value = "${instance.offset - instance.limit}"), + @Binding(name = "limit", value = "${instance.limit}") + }, + rel = "prev" + )}) + +public class ItemsRepresentation { + + + @XmlElement(name="items") + private List items; + + @XmlTransient + private int offset, limit; + + @XmlTransient + private ItemsModel itemsModel; + + + + + + @InjectLink( + resource = ItemsResource.class, + method = "query", + style = Style.ABSOLUTE, + bindings = {@Binding(name = "offset", value="${instance.offset}"), + @Binding(name = "limit", value="${instance.limit}") + }, + rel = "self" + ) + @XmlJavaTypeAdapter(Link.JaxbAdapter.class) + @XmlElement(name="link") + Link self; + + + + @InjectLinks({ + @InjectLink( + resource = ItemsResource.class, + style = Style.ABSOLUTE, + method = "query", + condition = "${instance.offset + instance.limit < instance.modelLimit}", + bindings = { + @Binding(name = "offset", value = "${instance.offset + instance.limit}"), + @Binding(name = "limit", value = "${instance.limit}") + }, + rel = "next" + ), + @InjectLink( + resource = ItemsResource.class, + style = Style.ABSOLUTE, + method = "query", + condition = "${instance.offset - instance.limit >= 0}", + bindings = { + @Binding(name = "offset", value = "${instance.offset - instance.limit}"), + @Binding(name = "limit", value = "${instance.limit}") + }, + rel = "prev" + )}) + @XmlElement(name="link") + @XmlElementWrapper(name = "links") + @XmlJavaTypeAdapter(Link.JaxbAdapter.class) + List links; + + + + + + public ItemsRepresentation() { + offset = 0; + limit = 10; + } + + public ItemsRepresentation(ItemsModel itemsModel, int offset, int limit) { + + this.offset = offset; + this.limit = limit; + this.itemsModel = itemsModel; + + items = new ArrayList(); + for (int i = offset; i < (offset + limit) && i < itemsModel.getSize(); i++) + { + items.add(new ItemRepresentation( + itemsModel, + Integer.toString(i), + itemsModel.getItem(Integer.toString(i)).getName())); + } + + } + + + + public int getOffset() + { + return offset; + } + + public int getLimit() + { + return limit; + } + + public int getModelLimit() + { + return itemsModel.getSize(); + } +} diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java index cc93ee96e5..701391f564 100644 --- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java @@ -57,7 +57,6 @@ * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ -@Path("items/{id}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public class ItemResource { @@ -65,9 +64,13 @@ public class ItemResource { private ItemModel itemModel; private String id; - public ItemResource(@PathParam("id") String id) { + public ItemResource() { + throw new IllegalStateException("Only for JAX-B dressing"); + } + + public ItemResource(ItemsModel itemsModel, String id) { this.id = id; - itemsModel = ItemsModel.getInstance(); + this.itemsModel = itemsModel; try { itemModel = itemsModel.getItem(id); } catch (IndexOutOfBoundsException ex) { @@ -77,32 +80,9 @@ public ItemResource(@PathParam("id") String id) { @GET public ItemRepresentation get() { - return new ItemRepresentation(itemModel.getName()); - } - - /** - * Determines whether there is a next item. - * @return - */ - public boolean isNext() { - return itemsModel.hasNext(id); - } - - /** - * Determines whether there is a previous item - * @return - */ - public boolean isPrev() { - return itemsModel.hasPrev(id); - } - - public String getNextId() { - return itemsModel.getNextId(id); + return new ItemRepresentation(itemsModel, id, itemModel.getName()); } - public String getPrevId() { - return itemsModel.getPrevId(id); - } public String getId() { return id; diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java new file mode 100644 index 0000000000..ea902f289d --- /dev/null +++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package org.glassfish.jersey.examples.linking.resources; + +import javax.ws.rs.DefaultValue; +import org.glassfish.jersey.examples.linking.model.ItemsModel; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.glassfish.jersey.examples.linking.representation.ItemsRepresentation; + +/** + * Resource that provides access to the entire list of items + * + * @author Mark Hadley + * @author Gerard Davison (gerard.davison at oracle.com) + */ +@Path("items") +@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) +public class ItemsResource { + + private ItemsModel itemsModel; + + public ItemsResource() { + itemsModel = ItemsModel.getInstance(); + } + + + @GET + public ItemsRepresentation query( + @Context javax.ws.rs.core.UriInfo info, + @QueryParam("offset") @DefaultValue("-1") int offset, @DefaultValue("-1") @QueryParam("limit") int limit) { + + + if (offset==-1 || limit == -1) + { + offset = offset == -1 ? 0 : offset; + limit = limit == -1 ? 10 : limit; + + throw new WebApplicationException( + Response.seeOther(info.getRequestUriBuilder().queryParam("offset", offset) + .queryParam("limit", limit).build()) + .build() + ); + } + + + + return new ItemsRepresentation(itemsModel, offset, limit ); + } + + + @Path("{id}") + public ItemResource get(@PathParam("id") String id ) { + return new ItemResource(itemsModel, id); + } + +} diff --git a/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java b/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java index 9c3f6a285b..fb559b95a2 100644 --- a/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java +++ b/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java @@ -41,11 +41,12 @@ package org.glassfish.jersey.examples.linking; import java.util.List; +import javax.ws.rs.core.MediaType; -import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; +import org.glassfish.jersey.client.ClientResponse; -import org.glassfish.jersey.examples.linking.resources.ItemResource; +import org.glassfish.jersey.examples.linking.resources.ItemsResource; import org.glassfish.jersey.linking.DeclarativeLinkingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; @@ -53,6 +54,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @author Naresh (Srinivas.Bhimisetty@Sun.Com) @@ -63,7 +65,7 @@ public class LinkWebAppTest extends JerseyTest { @Override protected ResourceConfig configure() { enable(TestProperties.LOG_TRAFFIC); - ResourceConfig rc = new ResourceConfig(ItemResource.class); + ResourceConfig rc = new ResourceConfig(ItemsResource.class); rc.register(DeclarativeLinkingFeature.class); return rc; } @@ -73,9 +75,16 @@ protected ResourceConfig configure() { */ @Test public void testLinks() throws Exception { - Response response = target().path("items/1").request().get(Response.class); + + Response response = target().path("items").queryParam("offset", 10).queryParam("limit", "10").request().accept(MediaType.APPLICATION_XML_TYPE).get(Response.class); + final Response.StatusType statusInfo = response.getStatusInfo(); + assertEquals("Should have succeeded", 200, statusInfo.getStatusCode()); + + + String content = response.readEntity(String.class); List linkHeaders = response.getHeaders().get("Link"); - assertEquals(2, linkHeaders.size()); + assertEquals("Should have two link headers", 2, linkHeaders.size()); + assertTrue("Content should contain next link",content.contains("http://localhost:9998/items?offset=20&limit=10")); } } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java index 0646cac31c..d89fa4aa09 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java @@ -43,32 +43,45 @@ import javax.ws.rs.core.Configuration; import javax.ws.rs.core.Feature; import javax.ws.rs.core.FeatureContext; + +import javax.inject.Singleton; + import org.glassfish.jersey.Beta; +import org.glassfish.jersey.linking.mapping.NaiveResourceMappingContext; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; + +import org.glassfish.hk2.utilities.binding.AbstractBinder; /** - * A feature to enable the declarative linking functionality + * A feature to enable the declarative linking functionality. * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ @Beta -public class DeclarativeLinkingFeature - implements Feature -{ +public class DeclarativeLinkingFeature implements Feature { @Override public boolean configure(FeatureContext context) { - + Configuration config = context.getConfiguration(); - - if (!config.isRegistered(LinkFilter.class)) - { - context.register(LinkFilter.class); + if (!config.isRegistered(ResponseLinkFilter.class)) { + context.register(new AbstractBinder() { + + @Override + protected void configure() { + bindAsContract(NaiveResourceMappingContext.class) + .to(ResourceMappingContext.class).in(Singleton.class); + } + }); + + context.register(ResponseLinkFilter.class); + +// Todo map values back? +// context.register(RequestLinkFilter.class); return true; } - return false; } - } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java index 049070b346..3f85d5e67e 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java @@ -40,43 +40,62 @@ package org.glassfish.jersey.linking; -import org.glassfish.jersey.linking.InjectLink; -import org.glassfish.jersey.linking.InjectLinkDescriptor; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.el.ExpressionFactory; -import javax.el.ValueExpression; + import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; + +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; + +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; import org.glassfish.jersey.uri.internal.UriTemplateParser; /** - * A helper class to build links from EL expressions - * + * A helper class to build links from EL expressions. + * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ -class ELLinkBuilder { +final class ELLinkBuilder { - private static ExpressionFactory expressionFactory = + private ELLinkBuilder() { + } + + private static final ExpressionFactory expressionFactory = ExpressionFactory.newInstance(); - public static boolean evaluateCondition(String condition, Object entity, - Object resource, Object instance) { - if (condition==null || condition.length()==0) + /** + * TODO javadoc. + */ + static boolean evaluateCondition(String condition, + Object entity, + Object resource, + Object instance) { + + if (condition == null || condition.isEmpty()) return true; LinkELContext context = new LinkELContext(entity, resource, instance); - ValueExpression expr = expressionFactory.createValueExpression(context, - condition, boolean.class); + ValueExpression expr = expressionFactory.createValueExpression(context, condition, boolean.class); + Object result = expr.getValue(context).toString(); - return result.equals("true"); + return "true".equals(result); } - public static URI buildURI(InjectLinkDescriptor link, Object entity, Object resource, Object instance, - UriInfo uriInfo) { - String template = link.getLinkTemplate(); + /** + * TODO javadoc. + */ + static URI buildURI(InjectLinkDescriptor link, + Object entity, + Object resource, + Object instance, + UriInfo uriInfo, + ResourceMappingContext rmc) { + + String template = link.getLinkTemplate(rmc); // first process any embedded EL expressions LinkELContext context = new LinkELContext(entity, resource, instance); @@ -85,16 +104,15 @@ public static URI buildURI(InjectLinkDescriptor link, Object entity, Object reso template = expr.getValue(context).toString(); // now process any embedded URI template parameters - UriBuilder ub=applyLinkStyle(template, link.getLinkStyle(), uriInfo); + UriBuilder ub = applyLinkStyle(template, link.getLinkStyle(), uriInfo); UriTemplateParser parser = new UriTemplateParser(template); List parameterNames = parser.getNames(); Map valueMap = getParameterValues(parameterNames, link, context); - URI uri = ub.buildFromMap(valueMap); - return uri; + return ub.buildFromMap(valueMap); } private static UriBuilder applyLinkStyle(String template, InjectLink.Style style, UriInfo uriInfo) { - UriBuilder ub=null; + UriBuilder ub = null; switch (style) { case ABSOLUTE: ub = uriInfo.getBaseUriBuilder().path(template); @@ -111,28 +129,23 @@ private static UriBuilder applyLinkStyle(String template, InjectLink.Style style } private static Map getParameterValues(List parameterNames, InjectLinkDescriptor linkField, LinkELContext context) { - Map values = new HashMap(); - for (String name: parameterNames) { + Map values = new HashMap<>(); + for (String name : parameterNames) { String elExpression = getEL(name, linkField); ValueExpression expr = expressionFactory.createValueExpression(context, elExpression, String.class); + Object value = expr.getValue(context); - values.put(name, value); + values.put(name, value != null ? value.toString() : null); } return values; } private static String getEL(String name, InjectLinkDescriptor linkField) { String binding = linkField.getBinding(name); - if (binding != null) + if (binding != null) { return binding; - StringBuilder builder = new StringBuilder(); - builder.append("${"); - builder.append(ResponseContextResolver.INSTANCE_OBJECT); - builder.append("."); - builder.append(name); - builder.append("}"); - return builder.toString(); + } + return "${" + ResponseContextResolver.INSTANCE_OBJECT + "." + name + "}"; } - } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java index 9f2a6bc534..00c603bad8 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java @@ -49,10 +49,10 @@ import java.util.List; import java.util.Set; import javax.ws.rs.core.Link; - import javax.ws.rs.core.UriInfo; -import org.glassfish.jersey.linking.LinkMessages; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; +import org.glassfish.jersey.server.ExtendedUriInfo; /** * Utility class that can inject links into {@link com.sun.jersey.server.linking.Link} annotated fields in @@ -76,10 +76,10 @@ public FieldProcessor(Class c) { * @param entity the entity object returned by the resource method * @param uriInfo the uriInfo for the request */ - public void processLinks(T entity, UriInfo uriInfo) { + public void processLinks(T entity, UriInfo uriInfo, ResourceMappingContext rmc) { Set processed = new HashSet(); Object resource = uriInfo.getMatchedResources().get(0); - processLinks(entity, resource, entity, processed, uriInfo); + processLinks(entity, resource, entity, processed, uriInfo, rmc); } /** @@ -91,7 +91,8 @@ public void processLinks(T entity, UriInfo uriInfo) { * @param uriInfo */ private void processLinks(Object entity, Object resource, Object instance, - Set processed, UriInfo uriInfo) { + Set processed, UriInfo uriInfo, + ResourceMappingContext rmc) { try { if (instance == null || processed.contains(instance)) @@ -110,7 +111,7 @@ private void processLinks(Object entity, Object resource, Object instance, { InjectLinkFieldDescriptor linkField = (InjectLinkFieldDescriptor) field; if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) { - URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo); + URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc); linkField.setPropertyValue(instance, uri); } } else if (field instanceof InjectLinksFieldDescriptor) { @@ -120,7 +121,7 @@ private void processLinks(Object entity, Object resource, Object instance, for (InjectLinkFieldDescriptor linkField : linksField.getLinksToInject()) { if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) { - URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo); + URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc); Link link = linkField.getLink(uri); list.add(link); } @@ -137,26 +138,27 @@ private void processLinks(Object entity, Object resource, Object instance, if (instanceClass.isArray() && Object[].class.isAssignableFrom(instanceClass)) { Object array[] = (Object[]) instance; for (Object member : array) { - processMember(entity, resource, member, processed, uriInfo); + processMember(entity, resource, member, processed, uriInfo,rmc); } } else if (instance instanceof Collection) { Collection collection = (Collection) instance; for (Object member : collection) { - processMember(entity, resource, member, processed, uriInfo); + processMember(entity, resource, member, processed, uriInfo,rmc); } } // Recursively process all member fields for (FieldDescriptor member : instanceDescriptor.getNonLinkFields()) { - processMember(entity, resource, member.getFieldValue(instance), processed, uriInfo); + processMember(entity, resource, member.getFieldValue(instance), processed, uriInfo,rmc); } } - private void processMember(Object entity, Object resource, Object member, Set processed, UriInfo uriInfo) { + private void processMember(Object entity, Object resource, Object member, Set processed, UriInfo uriInfo, + ResourceMappingContext rmc) { if (member != null) { FieldProcessor proc = new FieldProcessor(member.getClass()); - proc.processLinks(entity, resource, member, processed, uriInfo); + proc.processLinks(entity, resource, member, processed, uriInfo, rmc); } } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java index 0622b2870a..8216f3e254 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java @@ -47,6 +47,8 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; +import org.glassfish.jersey.server.ExtendedUriInfo; /** @@ -70,14 +72,14 @@ public HeaderProcessor(Class c) { * @param uriInfo the uriInfo for the request * @param headers the map into which the headers will be added */ - public void processLinkHeaders(T entity, UriInfo uriInfo, MultivaluedMap headers) { - List headerValues = getLinkHeaderValues(entity, uriInfo); + public void processLinkHeaders(T entity, UriInfo uriInfo, ResourceMappingContext rmc, MultivaluedMap headers) { + List headerValues = getLinkHeaderValues(entity, uriInfo,rmc); for (String headerValue: headerValues) { headers.add("Link", headerValue); } } - List getLinkHeaderValues(Object entity, UriInfo uriInfo) { + List getLinkHeaderValues(Object entity, UriInfo uriInfo, ResourceMappingContext rmc) { final List matchedResources = uriInfo.getMatchedResources(); if (!matchedResources.isEmpty()) { @@ -86,7 +88,7 @@ List getLinkHeaderValues(Object entity, UriInfo uriInfo) { for (LinkHeaderDescriptor desc: instanceDescriptor.getLinkHeaders()) { if (ELLinkBuilder.evaluateCondition(desc.getCondition(), entity, resource, entity)) { - String headerValue = getLinkHeaderValue(desc, entity, resource, uriInfo); + String headerValue = getLinkHeaderValue(desc, entity, resource, uriInfo, rmc); headerValues.add(headerValue); } } @@ -96,8 +98,9 @@ List getLinkHeaderValues(Object entity, UriInfo uriInfo) { return Collections.emptyList(); } - static String getLinkHeaderValue(LinkHeaderDescriptor desc, Object entity, Object resource, UriInfo uriInfo) { - URI uri = ELLinkBuilder.buildURI(desc, entity, resource, entity, uriInfo); + static String getLinkHeaderValue(LinkHeaderDescriptor desc, Object entity, Object resource, UriInfo uriInfo, + ResourceMappingContext rmc) { + URI uri = ELLinkBuilder.buildURI(desc, entity, resource, entity, uriInfo, rmc); InjectLink link = desc.getLinkHeader(); return InjectLink.Util.buildLinkFromUri(uri, link).toString(); } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java index e4d5188b59..58d88b0c42 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java @@ -41,6 +41,7 @@ package org.glassfish.jersey.linking; import org.glassfish.jersey.linking.InjectLink; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; /** * Utility for working with @Ref annotations @@ -60,7 +61,7 @@ interface InjectLinkDescriptor { * @Path of the class referenced in resource() * @return the link template */ - String getLinkTemplate(); + String getLinkTemplate(ResourceMappingContext rmc); /** * Get the binding as an EL expression for a particular URI template parameter diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java index a24e57ddf3..444cc14d74 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java @@ -40,6 +40,7 @@ package org.glassfish.jersey.linking; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.net.URI; import java.util.HashMap; @@ -47,15 +48,19 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.ws.rs.HttpMethod; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Link; + +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; import org.glassfish.jersey.server.model.AnnotatedMethod; - import org.glassfish.jersey.server.model.MethodList; - + /** - * Utility class for working with {@link InjectLink} annotated fields - * + * Utility class for working with {@link InjectLink} annotated fields. + * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ @@ -65,80 +70,118 @@ class InjectLinkFieldDescriptor extends FieldDescriptor implements InjectLinkDes private Class type; private Map bindings; + /** + * TODO javadoc. + */ public InjectLinkFieldDescriptor(Field f, InjectLink l, Class t) { super(f); link = l; type = t; - bindings = new HashMap(); - for (Binding binding: l.bindings()) { + bindings = new HashMap<>(); + for (Binding binding : l.bindings()) { bindings.put(binding.name(), binding.value()); } } - + + /** + * TODO javadoc. + */ public void setPropertyValue(Object instance, URI uri) { setAccessibleField(field); try { - - Object value = null; + + Object value; if (URI.class.equals(type)) { value = uri; - } - else if (Link.class.isAssignableFrom(type)) { + } else if (Link.class.isAssignableFrom(type)) { // Make a link with the correct bindings - // - value = getLink(uri); - } - else if (String.class.equals(type)) { + } else if (String.class.equals(type)) { value = uri.toString(); + } else { + throw new IllegalArgumentException("Field type " + type + " not one of supported String,URI and Link"); } - else { - throw new IllegalArgumentException("Field type " + type + " not one of supported String,URI and Link"); - } - + field.set(instance, value); - - - - - } catch (IllegalArgumentException ex) { - Logger.getLogger(InjectLinkFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException | IllegalAccessException ex) { Logger.getLogger(InjectLinkFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex); } } + /** + * TODO javadoc. + */ public InjectLink.Style getLinkStyle() { return link.style(); } - public String getLinkTemplate() { - return getLinkTemplate(link); + /** + * TODO javadoc. + */ + public String getLinkTemplate(ResourceMappingContext rmc) { + return getLinkTemplate(rmc, link); } - public static String getLinkTemplate(InjectLink link) { + + /** + * TODO javadoc. + */ + public static String getLinkTemplate(ResourceMappingContext rmc, InjectLink link) { String template = null; if (!link.resource().equals(Class.class)) { + + + ResourceMappingContext.Mapping map = rmc.getMapping(link.resource()); + if (map != null) { + template = map.getTemplate().getTemplate().toString(); + } else { + // extract template from specified class' @Path annotation + Path path = link.resource().getAnnotation(Path.class); + template = path == null ? "" : path.value(); + } + // extract template from specified class' @Path annotation - Path path = link.resource().getAnnotation(Path.class); - template = path==null ? "" : path.value(); - if (link.method().length() > 0) { + if (link.method().length() > 0) { // append value of method's @Path annotation MethodList methods = new MethodList(link.resource()); - methods = methods.withAnnotation(Path.class); + methods = methods.withMetaAnnotation(HttpMethod.class); Iterator iterator = methods.iterator(); while (iterator.hasNext()) { AnnotatedMethod method = iterator.next(); - if (!method.getMethod().getName().equals(link.method())) + if (!method.getMethod().getName().equals(link.method())) { continue; - Path methodPath = method.getAnnotation(Path.class); - String methodTemplate = methodPath.value(); + } StringBuilder builder = new StringBuilder(); builder.append(template); - if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) - builder.append("/"); - builder.append(methodTemplate); + + Path methodPath = method.getAnnotation(Path.class); + if (methodPath != null) { + String methodTemplate = methodPath.value(); + + if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) { + builder.append("/"); + } + builder.append(methodTemplate); + } + + // append query parameters + StringBuilder querySubString = new StringBuilder(); + for (Annotation paramAnns[] : method.getParameterAnnotations()) { + for (Annotation ann : paramAnns) { + if (ann.annotationType() == QueryParam.class) { + querySubString.append(((QueryParam) ann).value()); + querySubString.append(','); + } + } + } + + if (querySubString.length() > 0) { + builder.append("{?"); + builder.append(querySubString.subSequence(0, querySubString.length() - 1)); + builder.append("}"); + } + template = builder.toString(); break; } @@ -146,17 +189,27 @@ public static String getLinkTemplate(InjectLink link) { } else { template = link.value(); } + return template; } - + + /** + * TODO javadoc. + */ public Link getLink(URI uri) { return InjectLink.Util.buildLinkFromUri(uri, link); } - + + /** + * TODO javadoc. + */ public String getBinding(String name) { return bindings.get(name); } + /** + * TODO javadoc. + */ public String getCondition() { return link.condition(); } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java index 1253f1e597..b5e7a769d2 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java @@ -42,71 +42,65 @@ import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.net.URI; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import javax.ws.rs.Path; + import javax.ws.rs.core.Link; -import org.glassfish.jersey.server.model.AnnotatedMethod; - -import org.glassfish.jersey.server.model.MethodList; - + /** - * Utility class for working with {@link InjectLinks} annotated fields - * + * Utility class for working with {@link InjectLinks} annotated fields. + * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ -class InjectLinksFieldDescriptor extends FieldDescriptor { +class InjectLinksFieldDescriptor extends FieldDescriptor { - private InjectLinks link; - private Class type; + private final InjectLinks link; + private final Class type; + /** + * TODO javadoc. + */ public InjectLinksFieldDescriptor(Field f, InjectLinks l, Class t) { super(f); link = l; type = t; } - + + /** + * TODO javadoc. + */ public void setPropertyValue(Object instance, List list) { setAccessibleField(field); try { - - Object value = null; + + Object value; if (List.class.equals(type)) { value = list; + } else if (type.isArray()) { + value = list.toArray((Object[]) Array.newInstance(type.getComponentType(), list.size())); + } else { + throw new IllegalArgumentException("Field type " + type + " not one of supported List or List[]"); } - else if (type.isArray()) { - value = list.toArray((Object[])Array.newInstance(type.getComponentType(), list.size())); - } - else { - throw new IllegalArgumentException("Field type " + type + " not one of supported List or List[]"); - } - + field.set(instance, value); - - - - - } catch (IllegalArgumentException ex) { - Logger.getLogger(InjectLinksFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { + + + } catch (IllegalArgumentException | IllegalAccessException ex) { Logger.getLogger(InjectLinksFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex); } } - - public InjectLinkFieldDescriptor[] getLinksToInject() - { + + /** + * TODO javadoc. + */ + public InjectLinkFieldDescriptor[] getLinksToInject() { final InjectLink[] listOfLinks = link.value(); InjectLinkFieldDescriptor[] fields = new InjectLinkFieldDescriptor[listOfLinks.length]; - for (int i =0; i < fields.length; i ++) - { + for (int i = 0; i < fields.length; i++) { fields[i] = new InjectLinkFieldDescriptor(field, listOfLinks[i], Link.class); } - return fields; + return fields; } } diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java index 374dbb57fc..a4d2c6f6ec 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java @@ -43,6 +43,7 @@ import org.glassfish.jersey.linking.InjectLink.Style; import java.util.HashMap; import java.util.Map; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; /** * Utility class for working with {@link LinkHeader} annotations @@ -67,8 +68,8 @@ public InjectLink getLinkHeader() { return linkHeader; } - public String getLinkTemplate() { - return InjectLinkFieldDescriptor.getLinkTemplate(linkHeader); + public String getLinkTemplate(ResourceMappingContext rmc) { + return InjectLinkFieldDescriptor.getLinkTemplate(rmc, linkHeader); } public Style getLinkStyle() { diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java new file mode 100644 index 0000000000..69902d46c2 --- /dev/null +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package org.glassfish.jersey.linking; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Link; + +import org.glassfish.jersey.server.ExtendedUriInfo; + +/** + * Filter that processes {@link Link} annotated fields in returned response + * entities. + *

+ * When an application is deployed as a Servlet or Filter this filter can be + * registered using the following initialization parameters: + *

+ *     <init-param>
+ *         <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
+ *         <param-value>com.sun.jersey.server.linking.ResponseLinkFilter</param-value>
+ *     </init-param>
+ * 
+ *

+ * + * @author Mark Hadley + * @author Gerard Davison (gerard.davison at oracle.com) + * @see Link + */ + +class RequestLinkFilter implements ContainerRequestFilter { + + @Context + private ExtendedUriInfo uriInfo; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + + } +} diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java similarity index 87% rename from incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkFilter.java rename to incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java index 2e5a47c433..06114e34c1 100644 --- a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkFilter.java +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java @@ -37,17 +37,16 @@ * only if the new code is made subject to such option by the copyright * holder. */ - package org.glassfish.jersey.linking; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.UriInfo; - import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Link; -import javax.ws.rs.ext.Provider; +import javax.ws.rs.core.UriInfo; + +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; /** * Filter that processes {@link Link} annotated fields in returned response @@ -58,33 +57,36 @@ *

  *     <init-param>
  *         <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
- *         <param-value>com.sun.jersey.server.linking.LinkFilter</param-value>
+ *         <param-value>com.sun.jersey.server.linking.ResponseLinkFilter</param-value>
  *     </init-param>
  * 
*

* - * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) - * @see LinkHeader + * @see Link */ -@Provider -class LinkFilter implements ContainerResponseFilter { +class ResponseLinkFilter implements ContainerResponseFilter { @Context private UriInfo uriInfo; + @Context + private ResourceMappingContext rmc; + + @Override + @SuppressWarnings("unchecked") public void filter(ContainerRequestContext request, ContainerResponseContext response) { final Object entity = response.getEntity(); if (entity != null && !uriInfo.getMatchedResources().isEmpty()) { Class entityClass = entity.getClass(); HeaderProcessor lhp = new HeaderProcessor(entityClass); - lhp.processLinkHeaders(entity, uriInfo, response.getHeaders()); + lhp.processLinkHeaders(entity, uriInfo, rmc, response.getHeaders()); FieldProcessor lp = new FieldProcessor(entityClass); - lp.processLinks(entity, uriInfo); + lp.processLinks(entity, uriInfo, rmc); } } -} \ No newline at end of file +} diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java new file mode 100644 index 0000000000..0b7a72f75d --- /dev/null +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java @@ -0,0 +1,288 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package org.glassfish.jersey.linking.mapping; + +import java.lang.reflect.Type; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.Context; +import org.glassfish.jersey.process.Inflector; +import org.glassfish.jersey.server.ExtendedResourceContext; +import org.glassfish.jersey.server.model.HandlerConstructor; +import org.glassfish.jersey.server.model.Invocable; +import org.glassfish.jersey.server.model.MethodHandler; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.server.model.ResourceMethod; +import org.glassfish.jersey.server.model.ResourceModel; +import org.glassfish.jersey.server.model.ResourceModelComponent; +import org.glassfish.jersey.server.model.ResourceModelVisitor; +import org.glassfish.jersey.server.model.RuntimeResource; +import org.glassfish.jersey.uri.PathPattern; +import org.glassfish.jersey.uri.UriTemplate; + +/** + * This implementation of the resource mapping context assumed resource are + * of simple a simple type with a statically defined structure. + * @author Gerard Davison (gerard.davison at oracle.com) + */ + + +public class NaiveResourceMappingContext + implements ResourceMappingContext { + + + private ExtendedResourceContext erc; + + private Map, ResourceMappingContext.Mapping> mappings; + + + public NaiveResourceMappingContext(@Context ExtendedResourceContext erc) + { + this.erc = erc; + } + + + + @Override + public Mapping getMapping(Class resource) { + buildMappings(); + return mappings.get(resource); + } + + + + + + private void buildMappings() { + + if (mappings!=null) { + return; + } + + mappings + = new HashMap, ResourceMappingContext.Mapping>(); + + + // + + erc.getResourceModel().accept(new ResourceModelVisitor() { + + StringBuffer prefix = new StringBuffer(); + Deque stack = new LinkedList(); + + + private void processComponents(ResourceModelComponent component) { + + List components = component.getComponents(); + if (components!=null) + { + for (ResourceModelComponent rc : components) { + rc.accept(this); + } + } + } + + @Override + public void visitInvocable(Invocable invocable) { + processComponents(invocable); + } + + @Override + public void visitRuntimeResource(RuntimeResource runtimeResource) { + processComponents(runtimeResource); + } + + @Override + public void visitResourceModel(ResourceModel resourceModel) { + processComponents(resourceModel); + } + + + + @Override + public void visitResourceHandlerConstructor(HandlerConstructor handlerConstructor) { + processComponents(handlerConstructor); + } + + @Override + public void visitMethodHandler(MethodHandler methodHandler) { + processComponents(methodHandler); + } + + @Override + public void visitChildResource(Resource resource) { + visitResourceIntl(resource, false); + } + + @Override + public void visitResource(Resource resource) { + + visitResourceIntl(resource, true); + } + + + private void visitResourceIntl(Resource resource, boolean isRoot) { + try { + stack.addLast(resource.getPathPattern()); + processComponents(resource); + + if (isRoot) { + Class likelyToBeRoot = null; + for (Class next : resource.getHandlerClasses()) { + if (!(Inflector.class.isAssignableFrom(next))) { + likelyToBeRoot = next; + } + } + + if (likelyToBeRoot!=null){ + mappings.put(likelyToBeRoot, getMapping(getTemplate())); + } + } + } + finally { + stack.removeLast(); + } + } + + + + @Override + public void visitResourceMethod(ResourceMethod resourceMethod) { + + if (resourceMethod.isExtended()) { + return; + } + + if (ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR.equals(resourceMethod.getType())) { + if (resourceMethod.getInvocable()!=null) { + Invocable i = resourceMethod.getInvocable(); + + final Type type = i.getResponseType(); + final StringBuilder template = getTemplate(); + + mappings.put((Class)type, getMapping(template)); + + // Process sub resources ? + + try { + Resource.Builder builder = Resource + .builder(i.getRawResponseType()); + if (builder == null) { + // for example in the case the return type of the sub resource locator is Object + builder = Resource.builder().path(resourceMethod.getParent().getPath()); + } + Resource subResource = builder.build(); + + visitChildResource(subResource); + } + finally { + + } + + } + + + + } + + + processComponents(resourceMethod); + } + + private StringBuilder getTemplate() { + final StringBuilder template = new StringBuilder(); + for (PathPattern pp : stack) { + String ppTemplate = pp.getTemplate().getTemplate(); + + int tlength = template.length(); + if (tlength > 0) + { + if (template.charAt(tlength -1) == '/') { + if (ppTemplate.startsWith("/")) { + template.append(ppTemplate, 1, ppTemplate.length()); + } + else { + template.append(ppTemplate); + } + } + else { + if (ppTemplate.startsWith("/")) { + template.append(ppTemplate); + } + else { + template.append("/"); + template.append(ppTemplate); + } + } + } + else { + template.append(ppTemplate); + } + + + + } + return template; + } + + }); + + + + } + + private Mapping getMapping(final StringBuilder template) { + return new Mapping() + { + UriTemplate uriTemplate = new UriTemplate(template.toString()); + + @Override + public UriTemplate getTemplate() { + return uriTemplate; + } + + }; + } + +} diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java new file mode 100644 index 0000000000..ba17542759 --- /dev/null +++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2014 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package org.glassfish.jersey.linking.mapping; + +import org.glassfish.jersey.uri.UriTemplate; + +/** + * This service tries to work out the UriTemplate required for a particular + * resource class. + * + * @author Gerard Davison (gerard.davison at oracle.com) + */ + + +public interface ResourceMappingContext { + + public interface Mapping + { + public UriTemplate getTemplate(); + } + + + public Mapping getMapping(Class resource); +} diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java index 45aff29bd8..fb70345874 100644 --- a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java +++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java @@ -44,6 +44,7 @@ import java.util.Iterator; import javax.ws.rs.Path; import javax.ws.rs.core.UriBuilder; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; import org.junit.Test; import static org.junit.Assert.*; @@ -65,6 +66,15 @@ public static class TestClassA { public String baz; } + + ResourceMappingContext mockRmc = new ResourceMappingContext() { + + @Override + public ResourceMappingContext.Mapping getMapping(Class resource) { + return null; + } + }; + /** * Test for declared properties @@ -111,7 +121,7 @@ public void testResourceLink() { assertEquals(1, instance.getLinkFields().size()); assertEquals(0, instance.getNonLinkFields().size()); InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor)instance.getLinkFields().iterator().next(); - assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate()); + assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc)); assertEquals("baz", linkDesc.getBinding("bar")); } @@ -132,7 +142,7 @@ public void testStringLink() { Iterator i = instance.getLinkFields().iterator(); while (i.hasNext()) { InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor)i.next(); - assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate()); + assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc)); } } @@ -144,7 +154,7 @@ public void testSetLink() { TestClassD testClass = new TestClassD(); while (i.hasNext()) { InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor)i.next(); - URI value = UriBuilder.fromPath(linkDesc.getLinkTemplate()).build(); + URI value = UriBuilder.fromPath(linkDesc.getLinkTemplate(mockRmc)).build(); linkDesc.setPropertyValue(testClass, value); } assertEquals(TEMPLATE_A, testClass.res1); diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java index 4d80968d12..f83418ee2d 100644 --- a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java +++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java @@ -40,11 +40,6 @@ package org.glassfish.jersey.linking; -import org.glassfish.jersey.linking.FieldDescriptor; -import org.glassfish.jersey.linking.FieldProcessor; -import org.glassfish.jersey.linking.Binding; -import org.glassfish.jersey.linking.InjectLink; - import java.net.URI; import java.util.Arrays; import java.util.Collections; @@ -52,114 +47,202 @@ import java.util.logging.Filter; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.regex.MatchResult; import java.util.zip.ZipEntry; + import javax.ws.rs.GET; import javax.ws.rs.Path; -import javax.ws.rs.core.*; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Link; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.PathSegment; +import javax.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.server.model.ResourceMethod; +import org.glassfish.jersey.server.model.RuntimeResource; +import org.glassfish.jersey.uri.UriTemplate; -import static org.junit.Assert.*; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** - * * @author Mark Hadley * @author Gerard Davison (gerard.davison at oracle.com) */ public class FieldProcessorTest { - - UriInfo mockUriInfo = new UriInfo() { - private final static String baseURI = "http://example.com/application/resources"; + ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() { - public String getPath() { - throw new UnsupportedOperationException("Not supported yet."); - } + private final static String baseURI = "http://example.com/application/resources"; - public String getPath(boolean decode) { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public String getPath() { + throw new UnsupportedOperationException("Not supported yet."); + } - public List getPathSegments() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public String getPath(boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } - public List getPathSegments(boolean decode) { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public List getPathSegments() { + throw new UnsupportedOperationException("Not supported yet."); + } - public URI getRequestUri() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public List getPathSegments(boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } - public UriBuilder getRequestUriBuilder() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public URI getRequestUri() { + throw new UnsupportedOperationException("Not supported yet."); + } - public URI getAbsolutePath() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public UriBuilder getRequestUriBuilder() { + throw new UnsupportedOperationException("Not supported yet."); + } - public UriBuilder getAbsolutePathBuilder() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public URI getAbsolutePath() { + throw new UnsupportedOperationException("Not supported yet."); + } - public URI getBaseUri() { - return getBaseUriBuilder().build(); - } + @Override + public UriBuilder getAbsolutePathBuilder() { + throw new UnsupportedOperationException("Not supported yet."); + } - public UriBuilder getBaseUriBuilder() { - return UriBuilder.fromUri(baseURI); - } + @Override + public URI getBaseUri() { + return getBaseUriBuilder().build(); + } - public MultivaluedMap getPathParameters() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public UriBuilder getBaseUriBuilder() { + return UriBuilder.fromUri(baseURI); + } - public MultivaluedMap getPathParameters(boolean decode) { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public MultivaluedMap getPathParameters() { + throw new UnsupportedOperationException("Not supported yet."); + } - public MultivaluedMap getQueryParameters() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public MultivaluedMap getPathParameters(boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } - public MultivaluedMap getQueryParameters(boolean decode) { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public MultivaluedMap getQueryParameters() { + throw new UnsupportedOperationException("Not supported yet."); + } - public List getMatchedURIs() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public MultivaluedMap getQueryParameters(boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } - public List getMatchedURIs(boolean decode) { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + public List getMatchedURIs() { + throw new UnsupportedOperationException("Not supported yet."); + } - public List getMatchedResources() { - Object dummyResource = new Object(){}; - return Collections.singletonList(dummyResource); - } + @Override + public List getMatchedURIs(boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } - @Override - public URI resolve(URI uri) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } + @Override + public List getMatchedResources() { + Object dummyResource = new Object() { + }; + return Collections.singletonList(dummyResource); + } - @Override - public URI relativize(URI uri) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } + @Override + public Throwable getMappedThrowable() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getMatchedResults() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getMatchedTemplates() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getPathSegments(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getPathSegments(String name, boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getMatchedRuntimeResources() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ResourceMethod getMatchedResourceMethod() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Resource getMatchedModelResource() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getMatchedResourceLocators() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getLocatorSubResources() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public URI resolve(URI uri) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public URI relativize(URI uri) { + throw new UnsupportedOperationException("Not supported yet."); + } + }; - }; + private final ResourceMappingContext mockRmc = new ResourceMappingContext() { + + @Override + public ResourceMappingContext.Mapping getMapping(Class resource) { + return null; + } + }; private final static String TEMPLATE_A = "foo"; public static class TestClassD { - @InjectLink(value=TEMPLATE_A, style=InjectLink.Style.RELATIVE_PATH) + @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH) private String res1; - @InjectLink(value=TEMPLATE_A, style=InjectLink.Style.RELATIVE_PATH) + @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH) private URI res2; } @@ -168,7 +251,7 @@ public void testProcessLinks() { System.out.println("Links"); FieldProcessor instance = new FieldProcessor(TestClassD.class); TestClassD testClass = new TestClassD(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals(TEMPLATE_A, testClass.res1); assertEquals(TEMPLATE_A, testClass.res2.toString()); } @@ -176,7 +259,7 @@ public void testProcessLinks() { private final static String TEMPLATE_B = "widgets/{id}"; public static class TestClassE { - @InjectLink(value=TEMPLATE_B, style=InjectLink.Style.RELATIVE_PATH) + @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH) private String link; private String id; @@ -195,12 +278,12 @@ public void testProcessLinksWithFields() { System.out.println("Links from field values"); FieldProcessor instance = new FieldProcessor(TestClassE.class); TestClassE testClass = new TestClassE("10"); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("widgets/10", testClass.link); } public static class TestClassF { - @InjectLink(value=TEMPLATE_B, style=InjectLink.Style.RELATIVE_PATH) + @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH) private String thelink; private String id; @@ -222,7 +305,7 @@ public void testNesting() { FieldProcessor instance = new FieldProcessor(TestClassF.class); TestClassE nested = new TestClassE("10"); TestClassF testClass = new TestClassF("20", nested); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("widgets/20", testClass.thelink); assertEquals("widgets/10", testClass.nested.link); } @@ -234,7 +317,7 @@ public void testArray() { TestClassE item1 = new TestClassE("10"); TestClassE item2 = new TestClassE("20"); TestClassE array[] = {item1, item2}; - instance.processLinks(array, mockUriInfo); + instance.processLinks(array, mockUriInfo, mockRmc); assertEquals("widgets/10", array[0].link); assertEquals("widgets/20", array[1].link); } @@ -246,19 +329,19 @@ public void testCollection() { TestClassE item1 = new TestClassE("10"); TestClassE item2 = new TestClassE("20"); List list = Arrays.asList(item1, item2); - instance.processLinks(list, mockUriInfo); + instance.processLinks(list, mockUriInfo, mockRmc); assertEquals("widgets/10", list.get(0).link); assertEquals("widgets/20", list.get(1).link); } public static class TestClassG { - @InjectLink(value=TEMPLATE_B, style=InjectLink.Style.RELATIVE_PATH) + @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH) private String relativePath; - @InjectLink(value=TEMPLATE_B, style=InjectLink.Style.ABSOLUTE_PATH) + @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE_PATH) private String absolutePath; - @InjectLink(value=TEMPLATE_B, style=InjectLink.Style.ABSOLUTE) + @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE) private String absolute; @InjectLink(TEMPLATE_B) @@ -280,7 +363,7 @@ public void testLinkStyles() { System.out.println("Link styles"); FieldProcessor instance = new FieldProcessor(TestClassG.class); TestClassG testClass = new TestClassG("10"); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("widgets/10", testClass.relativePath); assertEquals("/application/resources/widgets/10", testClass.absolutePath); assertEquals("/application/resources/widgets/10", testClass.defaultStyle); @@ -301,7 +384,7 @@ public void testComputedProperty() { System.out.println("Computed property"); FieldProcessor instance = new FieldProcessor(TestClassH.class); TestClassH testClass = new TestClassH(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/widgets/10", testClass.link); } @@ -319,7 +402,7 @@ public void testEL() { System.out.println("EL link"); FieldProcessor instance = new FieldProcessor(TestClassI.class); TestClassI testClass = new TestClassI(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/widgets/10", testClass.link); } @@ -337,7 +420,7 @@ public void testMixed() { System.out.println("Mixed EL and template vars link"); FieldProcessor instance = new FieldProcessor(TestClassJ.class); TestClassJ testClass = new TestClassJ(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/widgets/10/widget/10", testClass.link); } @@ -346,6 +429,7 @@ public static class DependentInnerBean { public String outerUri; @InjectLink("${instance.id}") public String innerUri; + public String getId() { return "inner"; } @@ -353,6 +437,7 @@ public String getId() { public static class OuterBean { public DependentInnerBean inner = new DependentInnerBean(); + public String getId() { return "outer"; } @@ -363,13 +448,13 @@ public void testELScopes() { System.out.println("EL scopes"); FieldProcessor instance = new FieldProcessor(OuterBean.class); OuterBean testClass = new OuterBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/inner", testClass.inner.innerUri); assertEquals("/application/resources/outer", testClass.inner.outerUri); } public static class BoundLinkBean { - @InjectLink(value="{id}", bindings={@Binding(name="id", value="${instance.name}")}) + @InjectLink(value = "{id}", bindings = {@Binding(name = "id", value = "${instance.name}")}) public String uri; public String getName() { @@ -382,14 +467,14 @@ public void testELBinding() { System.out.println("EL binding"); FieldProcessor instance = new FieldProcessor(BoundLinkBean.class); BoundLinkBean testClass = new BoundLinkBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/name", testClass.uri); } - public static class BoundLinkOnLinkBean { - @InjectLink(value="{id}", - bindings={@Binding(name="id", value="${instance.name}")}, - rel="self") + public static class BoundLinkOnLinkBean { + @InjectLink(value = "{id}", + bindings = {@Binding(name = "id", value = "${instance.name}")}, + rel = "self") public Link link; public String getName() { @@ -402,36 +487,35 @@ public void testELBindingOnLink() { System.out.println("EL binding"); FieldProcessor instance = new FieldProcessor(BoundLinkOnLinkBean.class); BoundLinkOnLinkBean testClass = new BoundLinkOnLinkBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/name", testClass.link.getUri().toString()); assertEquals("self", testClass.link.getRel()); } - - public static class BoundLinkOnLinksBean { + + public static class BoundLinkOnLinksBean { @InjectLinks({ - @InjectLink(value="{id}", - bindings={@Binding(name="id", value="${instance.name}")}, - rel="self"), - @InjectLink(value="{id}", - bindings={@Binding(name="id", value="${instance.name}")}, - rel="other"), - - }) + @InjectLink(value = "{id}", + bindings = {@Binding(name = "id", value = "${instance.name}")}, + rel = "self"), + @InjectLink(value = "{id}", + bindings = {@Binding(name = "id", value = "${instance.name}")}, + rel = "other"), + + }) public List links; - + @InjectLinks({ - @InjectLink(value="{id}", - bindings={@Binding(name="id", value="${instance.name}")}, - rel="self"), - @InjectLink(value="{id}", - bindings={@Binding(name="id", value="${instance.name}")}, - rel="other"), - - }) + @InjectLink(value = "{id}", + bindings = {@Binding(name = "id", value = "${instance.name}")}, + rel = "self"), + @InjectLink(value = "{id}", + bindings = {@Binding(name = "id", value = "${instance.name}")}, + rel = "other"), + + }) public Link[] linksArray; - - + public String getName() { return "name"; @@ -443,7 +527,7 @@ public void testELBindingOnLinks() { System.out.println("EL binding"); FieldProcessor instance = new FieldProcessor(BoundLinkOnLinksBean.class); BoundLinkOnLinksBean testClass = new BoundLinkOnLinksBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/name", testClass.links.get(0).getUri().toString()); assertEquals("self", testClass.links.get(0).getRel()); assertEquals("other", testClass.links.get(1).getRel()); @@ -451,16 +535,15 @@ public void testELBindingOnLinks() { assertEquals("/application/resources/name", testClass.linksArray[0].getUri().toString()); assertEquals("self", testClass.linksArray[0].getRel()); assertEquals("other", testClass.linksArray[1].getRel()); - + } - - - + + public static class ConditionalLinkBean { - @InjectLink(value="{id}", condition="${entity.uri1Enabled}") + @InjectLink(value = "{id}", condition = "${entity.uri1Enabled}") public String uri1; - @InjectLink(value="{id}", condition="${entity.uri2Enabled}") + @InjectLink(value = "{id}", condition = "${entity.uri2Enabled}") public String uri2; public String getId() { @@ -481,7 +564,7 @@ public void testCondition() { System.out.println("Condition"); FieldProcessor instance = new FieldProcessor(ConditionalLinkBean.class); ConditionalLinkBean testClass = new ConditionalLinkBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/name", testClass.uri1); assertEquals(null, testClass.uri2); } @@ -496,7 +579,7 @@ public String getB() { } public static class SubResourceBean { - @InjectLink(resource=SubResource.class, method="getB") + @InjectLink(resource = SubResource.class, method = "getB") public String uri; } @@ -505,10 +588,65 @@ public void testSubresource() { System.out.println("Subresource"); FieldProcessor instance = new FieldProcessor(SubResourceBean.class); SubResourceBean testClass = new SubResourceBean(); - instance.processLinks(testClass, mockUriInfo); + instance.processLinks(testClass, mockUriInfo, mockRmc); assertEquals("/application/resources/a/b", testClass.uri); } + @Path("a") + public static class QueryResource { + @Path("b") + @GET + public String getB(@QueryParam("query") String query, @QueryParam("query2") String query2) { + return "hello world"; + } + } + + public static class QueryResourceBean { + + public String getQueryParam() { + return queryExample; + } + + private String queryExample; + + public QueryResourceBean(String queryExample, String queryExample2) { + this.queryExample = queryExample; + this.queryExample2 = queryExample2; + } + + public String getQueryParam2() { + return queryExample2; + } + + private String queryExample2; + + + @InjectLink(resource = QueryResource.class, method = "getB", + bindings = { + @Binding(name = "query", value = "${instance.queryParam}"), + @Binding(name = "query2", value = "${instance.queryParam2}") + }) + public String uri; + } + + @Test + public void testQueryResource() { + System.out.println("QueryResource"); + FieldProcessor instance = new FieldProcessor(QueryResourceBean.class); + QueryResourceBean testClass = new QueryResourceBean("queryExample", null); + instance.processLinks(testClass, mockUriInfo, mockRmc); + assertEquals("/application/resources/a/b?query=queryExample&query2=", testClass.uri); + } + + @Test + public void testDoubleQueryResource() { + System.out.println("QueryResource"); + FieldProcessor instance = new FieldProcessor(QueryResourceBean.class); + QueryResourceBean testClass = new QueryResourceBean("queryExample", "queryExample2"); + instance.processLinks(testClass, mockUriInfo, mockRmc); + assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri); + } + public static class TestClassK { public static final ZipEntry zipEntry = new ZipEntry("entry"); } @@ -519,9 +657,10 @@ public static class TestClassL { private class LoggingFilter implements Filter { private int count = 0; + @Override public synchronized boolean isLoggable(LogRecord logRecord) { - if(logRecord.getThrown() instanceof IllegalAccessException) { + if (logRecord.getThrown() instanceof IllegalAccessException) { count++; return false; } @@ -542,13 +681,13 @@ public void testKL() { FieldProcessor instanceK = new FieldProcessor(TestClassK.class); TestClassK testClassK = new TestClassK(); - instanceK.processLinks(testClassK, mockUriInfo); + instanceK.processLinks(testClassK, mockUriInfo, mockRmc); assertTrue(lf.getCount() == 0); FieldProcessor instanceL = new FieldProcessor(TestClassL.class); TestClassL testClassL = new TestClassL(); - instanceL.processLinks(testClassL, mockUriInfo); + instanceL.processLinks(testClassL, mockUriInfo, mockRmc); assertTrue(lf.getCount() == 0); diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java index 7779f42573..664a7bf85b 100644 --- a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java +++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java @@ -44,10 +44,16 @@ import java.net.URI; import java.util.Collections; import java.util.List; +import java.util.regex.MatchResult; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.linking.mapping.ResourceMappingContext; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.server.model.ResourceMethod; +import org.glassfish.jersey.server.model.RuntimeResource; +import org.glassfish.jersey.uri.UriTemplate; import static org.junit.Assert.*; import org.junit.Test; @@ -60,7 +66,7 @@ */ public class HeaderProcessorTest { - UriInfo mockUriInfo = new UriInfo() { + ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() { private final static String baseURI = "http://example.com/application/resources"; @@ -143,8 +149,68 @@ public URI relativize(URI uri) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } + @Override + public Throwable getMappedThrowable() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getMatchedResults() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getMatchedTemplates() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getPathSegments(String name) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getPathSegments(String name, boolean decode) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getMatchedRuntimeResources() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public ResourceMethod getMatchedResourceMethod() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Resource getMatchedModelResource() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getMatchedResourceLocators() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getLocatorSubResources() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + }; + + ResourceMappingContext mockRmc = new ResourceMappingContext() { + + @Override + public ResourceMappingContext.Mapping getMapping(Class resource) { + return null; + } + }; + + @InjectLink(value="A") public static class EntityA { @@ -155,7 +221,7 @@ public void testLiteral() { System.out.println("Literal"); HeaderProcessor instance = new HeaderProcessor(EntityA.class); EntityA testClass = new EntityA(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(1, headerValues.size()); String headerValue = headerValues.get(0); assertEquals("", headerValue); @@ -173,7 +239,7 @@ public void testEL() { System.out.println("EL"); HeaderProcessor instance = new HeaderProcessor(EntityB.class); EntityB testClass = new EntityB(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(1, headerValues.size()); String headerValue = headerValues.get(0); assertEquals("", headerValue); @@ -191,7 +257,7 @@ public void testTemplateLiteral() { System.out.println("Template Literal"); HeaderProcessor instance = new HeaderProcessor(EntityC.class); EntityC testClass = new EntityC(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(1, headerValues.size()); String headerValue = headerValues.get(0); assertEquals("", headerValue); @@ -209,7 +275,7 @@ public void testMultiple() { System.out.println("Multiple Literal"); HeaderProcessor instance = new HeaderProcessor(EntityD.class); EntityD testClass = new EntityD(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(2, headerValues.size()); // not sure if annotation order is supposed to be preserved but it seems // to work as expected @@ -240,7 +306,7 @@ public void testParameters() { System.out.println("Parameters"); HeaderProcessor instance = new HeaderProcessor(EntityE.class); EntityE testClass = new EntityE(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(1, headerValues.size()); String headerValue = headerValues.get(0); assertTrue(headerValue.contains("")); @@ -279,7 +345,7 @@ public void testConditional() { System.out.println("EL"); HeaderProcessor instance = new HeaderProcessor(EntityF.class); EntityF testClass = new EntityF(); - List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo); + List headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc); assertEquals(1, headerValues.size()); String headerValue = headerValues.get(0); assertEquals("", headerValue);