You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/09/08 15:15:05 UTC
[12/24] knox git commit: Merge branch 'master' into
KNOX-998-Package_Restructuring
http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Expander.java
----------------------------------------------------------------------
diff --cc gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Expander.java
index c27c005,0000000..7da797d
mode 100644,000000..100644
--- a/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Expander.java
+++ b/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Expander.java
@@@ -1,320 -1,0 +1,320 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.util.urltemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Expander {
+
+ private static Params EMPTY_PARAMS = new EmptyParams();
+
+ public static URI expand( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
+ return Expander.expandToUri( template, params, evaluator );
+ }
+
+ public static URI expandToUri( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
+ return new URI( expandToString( template, params, evaluator ) );
+ }
+
+ public static Template expandToTemplate( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
+ //TODO: This could be much more efficient if it didn't create and then parse a string.
+ return Parser.parseLiteral( expandToString( template, params, evaluator ) );
+ }
+
+ public static String expandToString( Template template, Params params, Evaluator evaluator ) {
+ StringBuilder builder = new StringBuilder();
+ if( params == null ) {
+ params = EMPTY_PARAMS;
+ }
+ Set<String> names = new HashSet<>( params.getNames() );
+ expandScheme( template, names, params, evaluator, builder );
+ expandAuthority( template, names, params, evaluator, builder );
+ expandPath( template, names, params, evaluator, builder );
+ if( template.hasFragment() ) {
+ StringBuilder fragment = new StringBuilder();
+ expandFragment( template, names, params, evaluator, fragment );
+ expandQuery( template, names, params, evaluator, builder );
+ builder.append( fragment );
+ } else {
+ expandQuery( template, names, params, evaluator, builder );
+ }
+ return builder.toString();
+ }
+
+ private static void expandScheme( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ Segment segment = template.getScheme();
+ if( segment != null ) {
+ expandSingleValue( template.getScheme(), names, params, evaluator, builder );
+ builder.append( ":" );
+ }
+ }
+
+ private static void expandAuthority( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ if( template.hasAuthority() ) {
+ if( !template.isAuthorityOnly() ) {
+ builder.append( "//" );
+ }
+ Segment username = template.getUsername();
+ Segment password = template.getPassword();
+ Segment host = template.getHost();
+ Segment port = template.getPort();
+ expandSingleValue( username, names, params, evaluator, builder );
+ if( password != null ) {
+ builder.append( ":" );
+ expandSingleValue( password, names, params, evaluator, builder );
+ }
+ if( username != null || password != null ) {
+ builder.append( "@" );
+ }
+ if( host != null ) {
+ expandSingleValue( host, names, params, evaluator, builder );
+ }
+ if( port != null ) {
+ builder.append( ":" );
+ expandSingleValue( port, names, params, evaluator, builder );
+ }
+ }
+ }
+
+ private static void expandPath( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ if( template.isAbsolute() ) {
+ builder.append( "/" );
+ }
+ List<Path> path = template.getPath();
+ for( int i=0, n=path.size(); i<n; i++ ) {
+ if( i > 0 ) {
+ builder.append( "/" );
+ }
+ Path segment = path.get( i );
+ String name = segment.getParamName();
+ Function function = new Function( name );
+ names.remove( function.getParameterName() );
+ Segment.Value value = segment.getFirstValue();
+ switch( value.getType() ) {
+ case( Segment.STATIC ):
+ String pattern = value.getOriginalPattern();
+ builder.append( pattern );
+ break;
+ case( Segment.DEFAULT ):
+ case( Segment.STAR ):
+ case( Segment.GLOB ):
+ case( Segment.REGEX ):
+ List<String> values = function.evaluate( params, evaluator );
+ expandPathValues( segment, values, builder );
+ break;
+ }
+ }
- if( template.isDirectory() && path.size() > 0 ) {
++ if( template.isDirectory() && !path.isEmpty() ) {
+ builder.append( "/" );
+ }
+ }
+
+ //TODO: This needs to handle multiple values but only to the limit of the segment.
+ private static void expandPathValues( Path segment, List<String> values, StringBuilder builder ) {
- if( values != null && values.size() > 0 ) {
++ if( values != null && !values.isEmpty() ) {
+ int type = segment.getFirstValue().getType();
+ if( type == Segment.GLOB || type == Segment.DEFAULT ) {
+ for( int i=0, n=values.size(); i<n; i++ ) {
+ if( i > 0 ) {
+ builder.append( "/" );
+ }
+ builder.append( values.get( i ) );
+ }
+ } else {
+ builder.append( values.get( 0 ) );
+ }
+ } else {
+ builder.append( segment.getFirstValue().getOriginalPattern() );
+ }
+ }
+
+ private static void expandQuery( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ AtomicInteger index = new AtomicInteger( 0 );
+ expandExplicitQuery( template, names, params, evaluator, builder, index );
+ expandExtraQuery( template, names, params, builder, index );
+ //Kevin: I took this out because it causes '?' to be added to expanded templates when there are not query params.
+// if( template.hasQuery() && index.get() == 0 ) {
+// builder.append( '?' );
+// }
+ }
+
+ private static void expandExplicitQuery( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder, AtomicInteger index ) {
+ Collection<Query> query = template.getQuery().values();
+ if( !query.isEmpty() ) {
+ Iterator<Query> iterator = query.iterator();
+ while( iterator.hasNext() ) {
+ int i = index.incrementAndGet();
+ if( i == 1 ) {
+ builder.append( "?" );
+ } else {
+ builder.append( "&" );
+ }
+ Query segment = iterator.next();
+ String queryName = segment.getQueryName();
+ String paramName = segment.getParamName();
+ Function function = new Function( paramName );
+ names.remove( function.getParameterName() );
+ for( Segment.Value value: segment.getValues() ) {
+ switch( value.getType() ) {
+ case( Segment.STATIC ):
+ builder.append( queryName );
+ String pattern = value.getOriginalPattern();
+ if( pattern != null ) {
+ builder.append( "=" );
+ builder.append( pattern );
+ }
+ break;
+ case( Segment.DEFAULT ):
+ case( Segment.GLOB ):
+ case( Segment.STAR ):
+ case( Segment.REGEX ):
+ List<String> values = function.evaluate( params, evaluator );
+ expandQueryValues( segment, queryName, values, builder );
+ break;
+ default:
+ }
+ }
+ }
+ }
+ }
+
+ private static void expandExtraQuery( Template template, Set<String> names, Params params, StringBuilder builder, AtomicInteger index ) {
+ Query extra = template.getExtra();
+ if( extra != null ) {
+ // Need to copy to an array because we are going to modify the set while iterating.
+ String[] array = new String[ names.size() ];
+ names.toArray( array );
+ for( String name: array ) {
+ names.remove( name );
+ List<String> values = params.resolve( name );
+ if( values != null ) {
+ for( String value: values ) {
+ int i = index.incrementAndGet();
+ if( i == 1 ) {
+ builder.append( "?" );
+ } else {
+ builder.append( "&" );
+ }
+ appendQueryPart(name, builder);
+ if( value != null ) {
+ builder.append( "=" );
+ appendQueryPart(value, builder);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void expandQueryValues( Query segment, String queryName, List<String> values, StringBuilder builder ) {
+ String value;
+ if( values == null || values.size() == 0 ) {
+ builder.append( queryName );
+ } else {
+ int type = segment.getFirstValue().getType();
+ if( type == Segment.GLOB || type == Segment.DEFAULT ) {
+ for( int i=0, n=values.size(); i<n; i++ ) {
+ if( i > 0 ) {
+ builder.append( "&" );
+ }
+ appendQueryPart(queryName, builder);
+ value = values.get( i );
+ if( value != null ) {
+ builder.append( "=" );
+ appendQueryPart(value, builder);
+ }
+ }
+ } else {
+ appendQueryPart(queryName, builder);
+ value = values.get( 0 );
+ if( value != null ) {
+ builder.append( "=" );
+ appendQueryPart(value, builder);
+ }
+ }
+ }
+ }
+
+ private static void appendQueryPart(String part, StringBuilder builder) {
+ try {
+ builder.append(URLEncoder.encode(part, "UTF-8"));
+ } catch ( UnsupportedEncodingException e ) {
+ builder.append(part);
+ }
+ }
+
+ private static void expandFragment( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ if( template.hasFragment() ) {
+ builder.append( "#" );
+ }
+ expandSingleValue( template.getFragment(), names, params, evaluator, builder );
+ }
+
+ private static void expandSingleValue( Segment segment, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
+ if( segment != null ) {
+ String paramName = segment.getParamName();
+ Function function = new Function( paramName );
+ names.remove( function.getParameterName() );
+ Segment.Value value = segment.getFirstValue();
+ String str;
+ switch( value.getType() ) {
+ case Segment.DEFAULT:
+ case Segment.STAR:
+ case Segment.GLOB:
+ case Segment.REGEX:
+ List<String> values = function.evaluate( params, evaluator );
+ if( values != null && !values.isEmpty() ) {
+ str = values.get( 0 );
+ } else if( function.getFunctionName() != null ) {
+ str = paramName;
+ } else {
+ str = value.getOriginalPattern();
+ }
+ break;
+ default:
+ str = value.getOriginalPattern();
+ break;
+ }
+ builder.append( str );
+ }
+ }
+
+ private static class EmptyParams implements Params {
+ @Override
+ public Set<String> getNames() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public List<String> resolve( String name ) {
+ return Collections.emptyList();
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Matcher.java
----------------------------------------------------------------------
diff --cc gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Matcher.java
index 0a18529,0000000..f13ab07
mode 100644,000000..100644
--- a/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Matcher.java
+++ b/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Matcher.java
@@@ -1,521 -1,0 +1,521 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.util.urltemplate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/*
+ Path
+ Match
+ {path} => {path=*}
+ {path=*} // Match single path level. (ie wildcard)
+ {path=**} // Match multiple path levels. (ie glob)
+ {path=*.ext} // Match single level with simplified regex pattern.
+ Expand
+ {path} => {path=**} // Note: Default cardinality changes between match and expand.
+ Query
+ Match
+ {queryParam} => {queryParam=*:queryParam}
+ {queryParam=*} => {queryParam=*:queryParam} // Match single queryParam value.
+ {queryParam=**} => {queryParam=**:queryParam} // Match multiple queryParam values.
+ {queryParam=*suffix:other-queryParam}
+ Expand
+ {queryParam} -> {queryParam=**:queryParam} // Note: Default cardinality changes between match and expand.
+ {queryParam=*} -> {queryParam=*:queryParam}
+ {queryParam=**} -> {queryParam=**:queryParam}
+ {queryParam=other-parm} -> {queryParam=**:otherparam} // Note: Default cardinality changes between match and expand.
+ {queryParam=:other-parm} -> {queryParam=**:otherparam} // Note: Default cardinality changes between match and expand.
+ {queryParam=*:other-parm} -> {queryParam=*:otherparam}
+ {queryParam=**:other-parm} -> {queryParam=**:otherparam}
+ */
+public class Matcher<V> {
+
+ private Map<Template,V> map;
+ private PathNode root;
+
+ public Matcher() {
+ map = new LinkedHashMap<Template,V>();
+ root = new PathNode( null, null );
+ }
+
+ public Matcher( Template template, V value ) {
+ this();
+ add( template, value );
+ }
+
+ public V get( Template template ) {
+ return map.get( template );
+ }
+
+ public void add( Template template, V value ) {
+ map.put( template, value );
+ PathNode node = root;
+
+ // Add the scheme segment to the tree (if any) while descending.
+ node = add( node, template.getScheme() );
+
+ // Add the authority segments (if any) while descending.
+ node = add( node, template.getUsername() );
+ node = add( node, template.getPassword() );
+ node = add( node, template.getHost() );
+ node = add( node, template.getPort() );
+
+ // Add the path segments while descending.
+ for( Path segment : template.getPath() ) {
+ // If the root already contains a matching segment then descend to that pathNode.
+ // Otherwise create a child pathNode, addValue it to the root and descend to it.
+ // If this new pathNode is a leaf pathNode then set the value.
+ node = add( node, segment );
+ }
+
+ // Add the fragment (if any) segments while descending.
+ // Note: Doing it this way puts the fragment above the query parameters in the match order.
+ node = add( node, template.getFragment() );
+
+ if( template.getQuery().isEmpty() && template.getExtra() == null ) {
+ // The first template with a value at this node wins.
+ if( node.value == null ) {
+ node.template = template;
+ node.value = value;
+ }
+ } else {
+ // Insert a query pathNode into the tree.
+ node.addQuery( template, value );
+ }
+ }
+
+ private PathNode add( PathNode parent, Segment segment ) {
+ PathNode child = parent;
+ if( segment != null ) {
+ if( ( parent.children != null ) && ( parent.children.containsKey( segment ) ) ) {
+ child = parent.children.get( segment );
+ } else {
+ child = parent.addPath( segment );
+ }
+ }
+ return child;
+ }
+
+ public Match match( Template input ) {
+ Status status = new Status();
+ status.candidates.add( new MatchSegment( null, root, null, null ) );
+ boolean matches = true;
+ // Separate &= statements for debugability.
+ matches &= matchScheme( input, status );
+ matches &= matchAuthority( input, status );
+ matches &= matchPath( input, status );
+ matches &= matchFragment( input, status );
+ Match winner;
+ if( matches ) {
+ winner = pickBestMatch( input, status );
+ } else {
+ winner = null;
+ }
+ return winner;
+ }
+
+ private boolean matchScheme( Template input, Status status ) {
+ pickMatchingChildren( input.getScheme(), status );
+ return status.hasCandidates();
+ }
+
+ private boolean matchAuthority( Template input, Status status ) {
+ pickMatchingChildren( input.getUsername(), status );
+ pickMatchingChildren( input.getPassword(), status );
+ pickMatchingChildren( input.getHost(), status );
+ // port does not makes sense without host
+ if(input.getHost() != null) {
+ // port is optional, since default ports do not need to present in URL
+ pickMatchingOptionalSegment(input.getPort(), status);
+ }
+ return status.hasCandidates();
+ }
+
+ private boolean matchPath( Template input, Status status ) {
+ Path segment;
+ Iterator<Path> segments = input.getPath().iterator();
+ while( segments.hasNext() && status.hasCandidates() ) {
+ segment = segments.next();
+ pickMatchingChildren( segment, status );
+ }
+ return status.hasCandidates();
+ }
+
+ private boolean matchFragment( Template input, Status status ) {
+ pickMatchingChildren( input.getFragment(), status );
+ return status.hasCandidates();
+ }
+
+ private void pickMatchingChildren( Segment segment, Status status ) {
+ if( segment != null ) {
+ for( MatchSegment parent : status.candidates ) {
+ if( parent.pathNode.hasGlob() ) {
+ status.matches.add( new MatchSegment( parent, parent.pathNode, parent.pathNode.segment, segment ) );
+ }
+ if( parent.pathNode.children != null ) {
+ for( PathNode node : parent.pathNode.children.values() ) {
+ if( node.matches( segment ) ) {
+ status.matches.add( new MatchSegment( parent, node, node.segment, segment ) );
+ }
+ }
+ }
+ }
+ status.swapMatchesToCandidates();
+ }
+ }
+
+ /**
+ * optional segment, if it does not present (it is null) it is accepted
+ */
+ private void pickMatchingOptionalSegment( Segment segment, Status status ) {
+ for( MatchSegment parent : status.candidates ) {
+ if( parent.pathNode.children != null ) {
+ for( PathNode node : parent.pathNode.children.values() ) {
+ if( segment != null ) {
+ if( node.matches( segment ) ) {
+ status.matches.add( new MatchSegment( parent, node, node.segment, segment ) );
+ }
+ } else {
+ status.matches.add( new MatchSegment( parent, node, node.segment, segment ) );
+ }
+ }
+ }
+ }
+ status.swapMatchesToCandidates();
+ }
+
+ private Match pickBestMatch( Template input, Status status ) {
+ Match bestMatch = new Match( null, null );
+ PathNode bestPath = null;
+ QueryNode bestQuery = null;
+ MatchSegment bestMatchSegment = null;
+ for( MatchSegment matchSegment: status.candidates ) {
+ PathNode pathNode = matchSegment.pathNode;
+ if( ( bestPath == null ) || // If we don't have anything at all pick the pathNode.
+ ( pathNode.depth > bestPath.depth ) || // If the pathNode is deeper than the best pathNode, pick it.
+ // If the pathNode is the same depth as current best but is static and the best isn't then pick it.
+ ( ( pathNode.depth == bestPath.depth ) && ( pathNode.getType() < bestPath.getType() ) ) ) {
+ // If the path node has a template then assume we will pick the path node.
+ if( pathNode.template != null ) {
+ bestPath = pathNode;
+ bestQuery = null;
+ bestMatch.template = pathNode.template;
+ bestMatch.value = pathNode.value;
+ bestMatchSegment = matchSegment;
+ }
+ // If the path node has queries see if one is better match than the path node itself.
+ if( pathNode.hasQueries() ) {
+ bestQuery = pickBestQueryMatch( input, pathNode );
+ if( bestQuery != null && bestQuery.template != null ) {
+ bestPath = pathNode;
+ bestMatch.template = bestQuery.template;
+ bestMatch.value = bestQuery.value;
+ bestMatchSegment = matchSegment;
+ }
+ }
+ }
+ }
+ Match match = createMatch( bestMatchSegment, bestPath, bestQuery, input );
+ return match;
+ }
+
+ private QueryNode pickBestQueryMatch( Template input, PathNode pathNode ) {
+ QueryNode bestNode = null;
+ int bestMatchCount = 0;
+ for( QueryNode node : pathNode.queries ) {
+ Query extra = node.template.getExtra();
+ int nodeQuerySize = node.template.getQuery().size();
+ int queryMatchCount = calcQueryMatchCount( node, input );
+ boolean matchesNamedQueries = queryMatchCount >= nodeQuerySize;
+ boolean matchesExtraQuery =
+ ( ( extra == null ) ||
+ ( Segment.GLOB_PATTERN.equals( extra.getQueryName() ) ) ||
+ ( input.getQuery().size() > nodeQuerySize ) );
+ if( ( bestNode == null || queryMatchCount > bestMatchCount ) && ( matchesNamedQueries && matchesExtraQuery ) ) {
+ bestMatchCount = queryMatchCount;
+ bestNode = node;
+ }
+ }
+ return bestNode;
+ }
+
+ private int calcQueryMatchCount( QueryNode node, Template input ) {
+ int matchCount = 0;
+ Map<String,Query> inputQuery = input.getQuery();
+ Map<String,Query> templateQuery = node.template.getQuery();
+ for( Query templateSegment : templateQuery.values() ) {
+ Query inputSegment = inputQuery.get( templateSegment.getQueryName() );
+ if( inputSegment != null && templateSegment.matches( inputSegment ) ) {
+ matchCount++ ;
+ } else {
+ matchCount = 0;
+ break;
+ }
+ }
+ return matchCount;
+ }
+
+ private Match createMatch( MatchSegment bestMatchSegment, PathNode bestPath, QueryNode bestQuery, Template input ) {
+ Match match = null;
+
+ if( bestPath != null ) { //&& ( bestQuery != null || !bestPath.hasQueries() ) ) {
+
+ if( bestQuery != null ) {
+ match = new Match( bestQuery.template, bestQuery.value );
+ } else {
+ match = new Match( bestPath.template, bestPath.value );
+ }
+
+ MatchParams matchParams = new MatchParams();
+
+ // Add the matching query segments to the end of the list.
+ if( bestQuery != null ) {
+ Map<String,Query> inputQuery = input.getQuery();
+ for( Query templateSegment : bestQuery.template.getQuery().values() ) {
+ Query inputSegment = inputQuery.get( templateSegment.getQueryName() );
+ if( inputSegment != null && templateSegment.matches( inputSegment ) ) {
+ extractSegmentParams( templateSegment, inputSegment, matchParams );
+ }
+ }
+ }
+
+ // If the template has the "extra" query queryParam then collect query params that were
+ // not already matched.
+ if( bestQuery != null ) {
+ Query extra = bestQuery.template.getExtra();
+ if( extra != null ) {
+ String paramName = extra.getParamName();
+ if( paramName != null && paramName.length() > 0 ) {
+ for( Query query: input.getQuery().values() ) {
+ String queryName = query.getQueryName();
+ if( matchParams.resolve( queryName ) == null ) {
+ for( Segment.Value value: query.getValues() ) {
+ matchParams.addValue( queryName, value.getEffectivePattern() );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Walk back up the matching segment tree.
+ MatchSegment matchSegment = bestMatchSegment;
+ while( matchSegment != null && matchSegment.pathNode.depth > 0 ) {
+ extractSegmentParams( matchSegment.templateSegment, matchSegment.inputSegment, matchParams );
+ matchSegment = matchSegment.parentMatch;
+ }
+ match.params = matchParams;
+ }
+ return match;
+ }
+
+ private void extractSegmentParams( Segment extractSegment, Segment inputSegment, MatchParams params ) {
+ if( extractSegment != null && inputSegment != null ) {
+ String paramName = extractSegment.getParamName();
+ if( paramName.length() > 0 ) {
+ for( Segment.Value value: inputSegment.getValues() ) {
+ params.insertValue( paramName, value.getEffectivePattern() );
+ }
+ }
+ }
+ }
+
+ private class Status {
+
+ List<MatchSegment> candidates = new ArrayList<MatchSegment>();
+ List<MatchSegment> matches = new ArrayList<MatchSegment>();
+ List<MatchSegment> temp;
+
+ private void swapMatchesToCandidates() {
+ temp = candidates; candidates = matches; matches = temp;
+ matches.clear();
+ }
+
+ private boolean hasCandidates() {
+ return !candidates.isEmpty();
+ }
+ }
+
+ private class MatchSegment {
+ private MatchSegment parentMatch;
+ private PathNode pathNode;
+ private Segment templateSegment;
+ private Segment inputSegment;
+
+ private MatchSegment( MatchSegment parent, PathNode node, Segment templateSegment, Segment inputSegment ) {
+ this.parentMatch = parent;
+ this.pathNode = node;
+ this.templateSegment = templateSegment;
+ this.inputSegment = inputSegment;
+ }
+ }
+
+ private class MatchParams implements Params {
+
+ private Map<String,List<String>> map = new HashMap<>();
+
+ public Set<String> getNames() {
+ return map.keySet();
+ }
+
+ private List<String> getOrAddValues( String name ) {
+ List<String> values = resolve( name );
+ if( values == null ) {
+ values = new ArrayList<String>( 1 );
+ map.put( name, values );
+ }
+ return values;
+ }
+
+ public void addValue( String name, String value ) {
+ List<String> values = getOrAddValues( name );
+ values.add( value );
+ }
+
+ public void insertValue( String name, String value ) {
+ List<String> values = getOrAddValues( name );
+ values.add( 0, value );
+ }
+
+ @Override
+ public List<String> resolve( String name ) {
+ return map.get( name );
+ }
+
+ }
+
+ public class Match {
+ private Template template;
+ private V value;
+ private Params params;
+ //TODO private Params extra;
+
+ private Match( Template template, V value ) {
+ this.template = template;
+ this.value = value;
+ }
+
+ public Template getTemplate() {
+ return template;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public Params getParams() {
+ return params;
+ }
+ }
+
+ private class PathNode extends Node {
+
+ int depth; // Zero based depth of the pathNode for "best pathNode" calculation.
+ Segment segment;
+ Map<Segment,PathNode> children;
+ Set<QueryNode> queries;
+
+ private PathNode( PathNode parent, Segment segment ) {
+ super( null, null );
+ this.depth = ( parent == null ) ? 0 : parent.depth+1;
+ this.segment = segment;
+ this.children = null;
+ this.queries = null;
+ }
+
+ private PathNode addPath( Segment path ) {
+ if( children == null ) {
+ children = new LinkedHashMap<Segment,PathNode>();
+ }
+ PathNode child = new PathNode( this, path );
+ children.put( path, child );
+ return child;
+ }
+
+ private QueryNode addQuery( Template template, V value ) {
+ if( queries == null ) {
+ queries = new LinkedHashSet<QueryNode>();
+ }
+ QueryNode query = new QueryNode( template, value );
+ queries.add( query );
+ return query;
+ }
+
+ private int getType() {
+ int type = Segment.UNKNOWN;
+ if( segment != null ) {
+ for( Segment.Value value: segment.getValues() ) {
+ int vType = value.getType();
+ type = type < vType ? type : vType;
+ if( type == Segment.STATIC ) {
+ break;
+ }
+ }
+ }
+ return type;
+ }
+
+ private boolean hasGlob() {
+ boolean is = false;
+ if( segment != null ) {
+ for( Segment.Value value: segment.getValues() ) {
+ if( Segment.GLOB == value.getType() ) {
+ is = true;
+ }
+ }
+ }
+ return is;
+ }
+
+ private boolean hasQueries() {
- return( queries != null && queries.size() > 0 );
++ return( queries != null && !queries.isEmpty() );
+ }
+
+ private boolean matches( Segment segment ) {
+ return( this.segment.matches( segment ) );
+ }
+
+ }
+
+ private class QueryNode extends Node {
+
+ private QueryNode( Template template, V value ) {
+ super( template, value );
+ }
+
+ }
+
+ private class Node {
+
+ Template template;
+ V value;
+
+ private Node( Template template, V value ) {
+ this.template = template;
+ this.value = value;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Template.java
----------------------------------------------------------------------
diff --cc gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Template.java
index 4ed85ef,0000000..e6ed1ab
mode 100644,000000..100644
--- a/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Template.java
+++ b/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Template.java
@@@ -1,345 -1,0 +1,345 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.util.urltemplate;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Template {
+
+ private String original;
+ private Scheme scheme;
+ private boolean hasScheme;
+ private Username username;
+ private Password password;
+ private Host host;
+ private Port port;
+ private boolean hasAuthority;
+ private boolean isAuthorityOnly;
+ private List<Path> path;
+ private boolean isAbsolute;
+ private boolean isDirectory;
+ private Map<String,Query> query;
+ private Query extra;
+ private boolean hasQuery;
+ private Fragment fragment;
+ private boolean hasFragment;
+ private Integer hash;
+
+ Template(
+ String original,
+ Scheme scheme,
+ boolean hasScheme,
+ Username username,
+ Password password,
+ Host host,
+ Port port,
+ boolean hasAuthority,
+ boolean isAuthorityOnly,
+ List<Path> path,
+ boolean isAbsolute,
+ boolean isDirectory,
+ LinkedHashMap<String,Query> query,
+ Query extra,
+ boolean hasQuery,
+ Fragment fragment,
+ boolean hasFragment ) {
+ this.original = original;
+ this.scheme = scheme;
+ this.hasScheme = hasScheme;
+ this.username = username;
+ this.password = password;
+ this.host = host;
+ this.port = port;
+ this.hasAuthority = hasAuthority;
+ this.isAuthorityOnly = isAuthorityOnly;
+ this.path = Collections.unmodifiableList( path );
+ this.isAbsolute = isAbsolute;
+ this.isDirectory = isDirectory;
+ this.query = Collections.unmodifiableMap( query );
+ this.extra = extra;
+ this.hasQuery = hasQuery;
+ this.fragment = fragment;
+ this.hasFragment = hasFragment;
+ this.hash = null;
+ }
+
+ public String getPattern() {
+ return original != null ? original : toString();
+ }
+
+ public Scheme getScheme() {
+ return scheme;
+ }
+
+ public boolean hasScheme() {
+ return hasScheme;
+ }
+
+ public Username getUsername() {
+ return username;
+ }
+
+ public Password getPassword() {
+ return password;
+ }
+
+ public Host getHost() {
+ return host;
+ }
+
+ public Port getPort() {
+ return port;
+ }
+
+ public boolean hasAuthority() {
+ return hasAuthority;
+ }
+
+ public boolean isAuthorityOnly() {
+ return isAuthorityOnly;
+ }
+
+ public List<Path> getPath() {
+ return path;
+ }
+
+ public boolean isAbsolute() {
+ return isAbsolute;
+ }
+
+ public boolean isDirectory() {
+ return isDirectory;
+ }
+
+ public Map<String,Query> getQuery() {
+ return query;
+ }
+
+ public Query getExtra() {
+ return extra;
+ }
+
+ public boolean hasQuery() {
+ return hasQuery;
+ }
+
+ public Fragment getFragment() {
+ return fragment;
+ }
+
+ public boolean hasFragment() {
+ return hasFragment;
+ }
+
+ private void buildScheme( StringBuilder b ) {
+ if( hasScheme ) {
+ if( scheme != null ) {
+ buildSegmentValue( b, scheme, scheme.getFirstValue() );
+ }
+ b.append( ':' );
+ }
+ }
+
+ private void buildAuthority( StringBuilder b ) {
+ if( hasAuthority ) {
+ if( !isAuthorityOnly ) {
+ b.append( "//" );
+ }
+ if( username != null || password != null ) {
+ if( username != null ) {
+ buildSegmentValue( b, username, username.getFirstValue() );
+ }
+ if( password != null ) {
+ b.append( ':' );
+ buildSegmentValue( b, password, password.getFirstValue() );
+ }
+ b.append( "@" );
+ }
+ if( host != null ) {
+ buildSegmentValue( b, host, host.getFirstValue() );
+ }
+ if( port != null ) {
+ b.append( ':' );
+ buildSegmentValue( b, port, port.getFirstValue() );
+ }
+ }
+ }
+
+ private void buildSegmentValue( StringBuilder b, Segment s, Segment.Value v ) {
+ String paramName = s.getParamName();
+ if( paramName != null && paramName.length() > 0 ) {
+ b.append( "{" );
+ b.append( s.getParamName() );
+ String actualPattern = v.getToken().originalPattern;
+ if( ( actualPattern != null ) && ( v.getType() != Segment.DEFAULT ) ) {
+ b.append( '=' );
+ b.append( v.getOriginalPattern() );
+ }
+ b.append( '}' );
+ } else {
+ b.append( s.getFirstValue().getOriginalPattern() );
+ }
+ }
+
+ private void buildPath( StringBuilder b ) {
+ if( isAbsolute ) {
+ b.append( '/' );
+ }
+ boolean first = true;
+ for( Path segment: path ) {
+ if( first ) {
+ first = false;
+ } else {
+ b.append( '/' );
+ }
+ String paramName = segment.getParamName();
+ Segment.Value firstValue = segment.getFirstValue();
+ if( paramName != null && paramName.length() > 0 ) {
+ b.append( "{" );
+ b.append( segment.getParamName() );
+ String pattern = firstValue.getOriginalPattern();
+ if( pattern != null && !pattern.isEmpty() ) {
+ b.append( '=' );
+ b.append( firstValue );
+ }
+ b.append( '}' );
+ } else {
+ b.append( firstValue.getOriginalPattern() );
+ }
+ }
- if( isDirectory && ( !isAbsolute || path.size() > 0 ) ) {
++ if( isDirectory && ( !isAbsolute || !path.isEmpty() ) ) {
+ b.append( '/' );
+ }
+ }
+
+ private void buildQuery( StringBuilder b ) {
+ if( hasQuery ) {
+ int count = 0;
+ for( Query segment: query.values() ) {
+// String paramName = segment.getParamName();
+ for( Segment.Value value: segment.getValues() ) {
+ count++;
+ if( count == 1 ) {
+ b.append( '?' );
+ } else {
+ b.append( '&' );
+ }
+ buildQuerySegment( b, segment, value );
+// String valuePattern = value.getPattern();
+// if( paramName != null && paramName.length() > 0 ) {
+// b.append( segment.getQueryName() );
+// b.append( "={" );
+// b.append( segment.getParamName() );
+// if( valuePattern != null ) {
+// b.append( '=' );
+// b.append( valuePattern );
+// }
+// b.append( '}' );
+// } else {
+// b.append( segment.getQueryName() );
+// if( valuePattern != null ) {
+// b.append( "=" );
+// b.append( valuePattern );
+// }
+// }
+ }
+ }
+ if( extra != null ) {
+ count++;
+ if( count == 1 ) {
+ b.append( '?' );
+ } else {
+ b.append( '&' );
+ }
+ buildQuerySegment( b, extra, extra.getFirstValue() );
+ }
+ if( count == 0 ) {
+ b.append( '?' );
+ }
+ }
+ }
+
+ private void buildQuerySegment( StringBuilder b, Query segment, Segment.Value value ) {
+ String paramName = segment.getParamName();
+ String queryName = segment.getQueryName();
+ String valuePattern = value.getOriginalPattern();
+ if( paramName != null && paramName.length() > 0 ) {
+ if( !Segment.GLOB_PATTERN.equals( queryName ) && !Segment.STAR_PATTERN.equals( queryName ) ) {
+ b.append( segment.getQueryName() );
+ b.append( "=" );
+ }
+ b.append( "{" );
+ b.append( segment.getParamName() );
+ if( valuePattern != null ) {
+ b.append( '=' );
+ b.append( valuePattern );
+ }
+ b.append( '}' );
+ } else {
+ b.append( queryName );
+ if( valuePattern != null ) {
+ b.append( "=" );
+ b.append( valuePattern );
+ }
+ }
+ }
+
+ private void buildFragment( StringBuilder b ) {
+ if( hasFragment ) {
+ b.append( '#' );
+ if( fragment != null ) {
+ b.append( fragment.getFirstValue().getOriginalPattern() );
+ }
+ }
+ }
+
+ public String toString() {
+ String s = null;
+ StringBuilder b = new StringBuilder();
+ buildScheme( b );
+ buildAuthority( b );
+ buildPath( b );
+ buildQuery( b );
+ buildFragment( b );
+ s = b.toString();
+ return s;
+ }
+
+ public int hashCode() {
+ Integer hc = hash;
+ if( hc == null ) {
+ hc = toString().hashCode();
+ hash = hc;
+ }
+ return hc.intValue();
+ }
+
+ public boolean equals( Object object ) {
+ boolean equals = false;
+ if( object != null && object instanceof Template ) {
+ String thisStr = toString();
+ String thatStr = object.toString();
+ equals = thisStr.equals( thatStr );
+ }
+ return equals;
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-util-urltemplate/src/test/java/org/apache/knox/gateway/util/urltemplate/ExpanderTest.java
----------------------------------------------------------------------
diff --cc gateway-util-urltemplate/src/test/java/org/apache/knox/gateway/util/urltemplate/ExpanderTest.java
index f75d242,0000000..acf7cf6
mode 100644,000000..100644
--- a/gateway-util-urltemplate/src/test/java/org/apache/knox/gateway/util/urltemplate/ExpanderTest.java
+++ b/gateway-util-urltemplate/src/test/java/org/apache/knox/gateway/util/urltemplate/ExpanderTest.java
@@@ -1,538 -1,0 +1,538 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.util.urltemplate;
+
+import org.apache.hadoop.test.category.FastTests;
+import org.apache.hadoop.test.category.UnitTests;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
- import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
++import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+@Category( { UnitTests.class, FastTests.class } )
+public class ExpanderTest {
+
+ @Test
+ public void testHostAndPortOnlyExpansionBugKnox381() throws Exception {
+ String text = "{host}:{port}";
+ Template template = Parser.parseTemplate( text );
+ MockParams params = new MockParams();
+ params.addValue( "host", "test-host" );
+ params.addValue( "port", "777" );
+ URI expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "test-host:777" ) ) ;
+ }
+
+ @Test
+ public void testCompleteUrl() throws URISyntaxException {
+ String text;
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ text = "foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose";
+ template = Parser.parseLiteral( text );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( text ) ) ;
+
+ text = "{scheme}://{username}:{password}@{host}:{port}/{path=**}?query={queryParam}#{fragment}";
+ template = Parser.parseTemplate( text );
+ params = new MockParams();
+ params.addValue( "scheme", "http" );
+ params.addValue( "username", "horton" );
+ params.addValue( "password", "hadoop" );
+ params.addValue( "host", "hortonworks.com" );
+ params.addValue( "port", "8888" );
+ params.addValue( "path", "top" );
+ params.addValue( "path", "mid" );
+ params.addValue( "path", "bot" );
+ params.addValue( "path", "file" );
+ params.addValue( "queryParam", "new-value" );
+ params.addValue( "fragment", "fragment" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "http://horton:hadoop@hortonworks.com:8888/top/mid/bot/file?query=new-value#fragment" ) ) ;
+ }
+
+ @Test
+ public void testBasicExpansion() throws Exception {
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ template = Parser.parseTemplate( "" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "" ) ) ;
+
+ template = Parser.parseTemplate( "/" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/" ) ) ;
+
+ template = Parser.parseTemplate( "{path-name}" );
+ params = new MockParams();
+ params.addValue( "path-name", "path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "path-value" ) ) ;
+
+ template = Parser.parseTemplate( "/{path-name}" );
+ params = new MockParams();
+ params.addValue( "path-name", "path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/path-value" ) ) ;
+
+ template = Parser.parseTemplate( "{path-name}/" );
+ params = new MockParams();
+ params.addValue( "path-name", "path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "path-value/" ) ) ;
+
+ template = Parser.parseTemplate( "/{path-name}/" );
+ params = new MockParams();
+ params.addValue( "path-name", "path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/path-value/" ) ) ;
+
+ template = Parser.parseTemplate( "path-name" );
+ params = new MockParams();
+ params.addValue( "path-name", "other-path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "path-name" ) ) ;
+
+ template = Parser.parseTemplate( "/path-name" );
+ params = new MockParams();
+ params.addValue( "path-name", "other-path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/path-name" ) ) ;
+
+ template = Parser.parseTemplate( "path-name/" );
+ params = new MockParams();
+ params.addValue( "path-name", "other-path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "path-name/" ) ) ;
+
+ template = Parser.parseTemplate( "/path-name/" );
+ params = new MockParams();
+ params.addValue( "path-name", "other-path-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/path-name/" ) ) ;
+
+ template = Parser.parseTemplate( "?" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "" ) ) ;
+
+ template = Parser.parseTemplate( "?query-name={queryParam-name}" );
+ params = new MockParams();
+ params.addValue( "queryParam-name", "queryParam-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query-name=queryParam-value" ) ) ;
+
+ template = Parser.parseTemplate( "?query-name-1={queryParam-name-1}&query-name-2={queryParam-name-2}" );
+ params = new MockParams();
+ params.addValue( "queryParam-name-1", "queryParam-value-1" );
+ params.addValue( "queryParam-name-2", "queryParam-value-2" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query-name-1=queryParam-value-1&query-name-2=queryParam-value-2" ) ) ;
+
+ template = Parser.parseTemplate( "?query-name=queryParam-value" );
+ params = new MockParams();
+ params.addValue( "queryParam-name", "other-queryParam-value" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query-name=queryParam-value" ) ) ;
+
+ template = Parser.parseTemplate( "?query-name-1=queryParam-value-1&query-name-2=queryParam-value-2" );
+ params = new MockParams();
+ params.addValue( "queryParam-name-1", "other-queryParam-value-1" );
+ params.addValue( "queryParam-name-2", "other-queryParam-value-2" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query-name-1=queryParam-value-1&query-name-2=queryParam-value-2" ) ) ;
+ }
+
+// Can't create a URI with just a scheme: new URI( "http:" )
+// @Test
+// public void testSchemeExpansion() throws URISyntaxException {
+// Template template;
+// Params params;
+// URI expanded;
+//
+// template = Parser.parse( "scheme:" );
+// params = new Params();
+// expanded = Expander.expand( template, params );
+// assertThat( expanded.toString(), equalTo( "scheme:" ) ) ;
+// }
+
+ @Test
+ public void testAuthorityExpansion() throws URISyntaxException {
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ template = Parser.parseTemplate( "//host" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "//host" ) ) ;
+
+ template = Parser.parseTemplate( "//:port" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "//:port" ) ) ;
+
+ template = Parser.parseTemplate( "//username@" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "//username@" ) ) ;
+
+ template = Parser.parseTemplate( "//:password@" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "//:password@" ) ) ;
+ }
+
+ @Test
+ public void testPathExpansion() throws URISyntaxException {
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ template = Parser.parseTemplate( "/a/b/c" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/a/b/c" ) ) ;
+
+ template = Parser.parseTemplate( "/top/{middle}/bottom" );
+ params = new MockParams();
+ params.addValue( "middle", "A" );
+ params.addValue( "middle", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/top/A/B/bottom" ) ) ;
+
+ template = Parser.parseTemplate( "/top/{middle=*}/bottom" );
+ params = new MockParams();
+ params.addValue( "middle", "A" );
+ params.addValue( "middle", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/top/A/bottom" ) ) ;
+
+ template = Parser.parseTemplate( "/top/{middle=**}/bottom" );
+ params = new MockParams();
+ params.addValue( "middle", "A" );
+ params.addValue( "middle", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "/top/A/B/bottom" ) ) ;
+ }
+
+ @Test
+ public void testQueryExpansion() throws URISyntaxException {
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ template = Parser.parseTemplate( "?query" );
+ params = new MockParams();
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query" ) ) ;
+
+ template = Parser.parseTemplate( "?query={queryParam}" );
+ params = new MockParams();
+ params.addValue( "queryParam", "A" );
+ params.addValue( "queryParam", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query=A&query=B" ) ) ;
+
+ template = Parser.parseTemplate( "?query={queryParam=*}" );
+ params = new MockParams();
+ params.addValue( "queryParam", "A" );
+ params.addValue( "queryParam", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query=A" ) ) ;
+
+ template = Parser.parseTemplate( "?query={queryParam=**}" );
+ params = new MockParams();
+ params.addValue( "queryParam", "A" );
+ params.addValue( "queryParam", "B" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "?query=A&query=B" ) ) ;
+
+ }
+
+ @Test
+ public void testExtraParamHandling() throws Exception {
+ String text;
+ Template template;
+ MockParams params;
+ URI expandedUri;
+ Template expandedTemplate;
+ String expandedString;
+
+ params = new MockParams();
+ params.addValue( "scheme", "schemeA" );
+ params.addValue( "host", "hostA" );
+ params.addValue( "query", "queryA" );
+ params.addValue( "query", "queryB" );
+ params.addValue( "path", "pathA" );
+ params.addValue( "path", "pathB" );
+ params.addValue( "extra", "extraA" );
+
+ text = "{scheme}://host/{path=*]?{query=*}";
+ template = Parser.parseTemplate( text );
+ expandedTemplate = Expander.expandToTemplate( template, params, null );
+ assertThat( expandedTemplate.toString(), equalTo( "schemeA://host/{path=*]?query=queryA" ) );
+ expandedString = Expander.expandToString( template, params, null );
+ assertThat( expandedString, equalTo( "schemeA://host/{path=*]?query=queryA" ) );
+ try {
+ expandedUri = Expander.expand( template, params, null );
+ fail( "Should have thrown exception" );
+ } catch( URISyntaxException e ) {
+ // Expected.
+ }
+
+ template = Parser.parseTemplate( "{scheme}://host/{path=**}?{query=**}" );
+ expandedUri = Expander.expand( template, params, null );
+ assertThat( expandedUri.toString(), equalTo( "schemeA://host/pathA/pathB?query=queryA&query=queryB" ) );
+
+ template = Parser.parseTemplate( "{scheme}://host/{path=**}?{host}&{query=**}&{**}" );
+ expandedUri = Expander.expand( template, params, null );
+ assertThat(
+ expandedUri.toString(),
+ equalTo( "schemeA://host/pathA/pathB?host=hostA&query=queryA&query=queryB&extra=extraA" ) );
+
+ template = Parser.parseTemplate( "{scheme}://host/{path=**}?server={host}&{query=**}&{**}" );
+ expandedUri = Expander.expand( template, params, null );
+ assertThat(
+ expandedUri.toString(),
+ equalTo( "schemeA://host/pathA/pathB?server=hostA&query=queryA&query=queryB&extra=extraA" ) );
+
+ // In this case "server-host" is treated as a param name and not found in the params so it
+ // is copied. I'm not really sure what the correct behavior should be. My initial thinking
+ // is that if something within {} isn't resolve to a param it should be dropped from the output.
+ template = Parser.parseTemplate( "{scheme}://host/{path=**}?{server=host}&{query=**}&{**}" );
+ expandedUri = Expander.expand( template, params, null );
+ expandedString = expandedUri.toString();
+ assertThat( expandedString, containsString( "schemeA://host/pathA/pathB?" ) );
+ assertThat( expandedString, containsString( "server=host" ) );
+ assertThat( expandedString, containsString( "query=queryA" ) );
+ assertThat( expandedString, containsString( "query=queryB" ) );
+ assertThat( expandedString, containsString( "host=hostA" ) );
+ assertThat( expandedString, containsString( "extra=extraA" ) );
+ assertThat( expandedString, containsString( "&" ) );
+ }
+
+
+ @Test
+ public void testBugKnox599() throws Exception {
+ String text;
+ Template template;
+ MockParams params;
+ URI expanded;
+
+ text = "{scheme}://{host}:{port}/{path=**}?{**}";
+ template = Parser.parseTemplate( text );
+ params = new MockParams();
+ params.addValue( "scheme", "http" );
+ params.addValue( "host", "hortonworks.com" );
+ params.addValue( "port", "8888" );
+ params.addValue( "path", "top" );
+ params.addValue( "path", "mid" );
+ params.addValue( "path", "bot" );
+ params.addValue( "path", "file" );
+ params.addValue( "name", "value" );
+ params.addValue( "flag", "" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "http://hortonworks.com:8888/top/mid/bot/file?flag=&name=value" ) ) ;
+
+ text = "{scheme}://{host}:{port}/{path=**}?{**}";
+ template = Parser.parseTemplate( text );
+ params = new MockParams();
+ params.addValue( "scheme", "http" );
+ params.addValue( "host", "hortonworks.com" );
+ params.addValue( "port", "8888" );
+ params.addValue( "path", "top" );
+ params.addValue( "path", "mid" );
+ params.addValue( "path", "bot" );
+ params.addValue( "path", "file" );
+ params.addValue( "name", "value" );
+ params.addValue( "flag", null );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "http://hortonworks.com:8888/top/mid/bot/file?flag&name=value" ) ) ;
+
+ text = "{scheme}://{host}:{port}/{path=**}?{name=*}&{**}";
+ template = Parser.parseTemplate( text );
+ params = new MockParams();
+ params.addValue( "scheme", "http" );
+ params.addValue( "host", "hortonworks.com" );
+ params.addValue( "port", "8888" );
+ params.addValue( "path", "top" );
+ params.addValue( "path", "mid" );
+ params.addValue( "path", "bot" );
+ params.addValue( "path", "file" );
+ params.addValue( "name", null );
+ params.addValue( "flag", "" );
+ expanded = Expander.expand( template, params, null );
+ assertThat( expanded.toString(), equalTo( "http://hortonworks.com:8888/top/mid/bot/file?name&flag=" ) ) ;
+ }
+
+ @Test
+ public void testValuelessQueryParamParsingAndExpansionBugKnox599Knox447() throws Exception {
+ URI inputUri, outputUri;
+ Matcher<Void> matcher;
+ Matcher<Void>.Match match;
+ Template input, pattern, template;
+ Evaluator evaluator;
+
+ inputUri = new URI( "https://knoxHost:8443/gateway/knoxTopo/templeton/v1/?version/hive" );
+
+ input = Parser.parseLiteral( inputUri.toString() );
+ pattern = Parser.parseTemplate( "*://*:*/**/templeton/v1/?{**}" );
+ template = Parser.parseTemplate( "{$serviceUrl[WEBHCAT]}/v1/?{**}" );
+
+ matcher = new Matcher<Void>();
+ matcher.add( pattern, null );
+ match = matcher.match( input );
+
+ evaluator = new Evaluator() {
+ @Override
+ public List<String> evaluate( String function, List<String> parameters ) {
+ return Arrays.asList( "https://webhcatTestHost.com:50111/templeton" );
+ }
+ };
+
+ outputUri = Expander.expand( template, match.getParams(), evaluator );
+ assertThat(
+ outputUri.toString(),
+ equalToIgnoringCase( "https://webhcatTestHost.com:50111/templeton/v1/?version%2Fhive" ) );
+
+ }
+
+ @Test
+ public void testRedirectHeaderRewriteKnoxBug614() throws Exception {
+ URI inputUri, outputUri;
+ Matcher<Void> matcher;
+ Matcher<Void>.Match match;
+ Template input, pattern, template;
+ Evaluator evaluator;
+
+ inputUri = new URI("https://internal-host:9443/context/?user.name=admin#/login");
+
+ input = Parser.parseLiteral( inputUri.toString() );
+ pattern = Parser.parseTemplate( "*://*:*/{contextRoot}/?{**}#{fragment}" );
+ template = Parser.parseTemplate( "{$gateway.url}/foo/{contextRoot}/?{**}#{fragment}" );
+
+ matcher = new Matcher<Void>();
+ matcher.add( pattern, null );
+ match = matcher.match( input );
+
+ evaluator = new Evaluator() {
+ @Override
+ public List<String> evaluate( String function, List<String> parameters ) {
+ return Arrays.asList( "https://gateway-host:9443/gateway/default" );
+ }
+ };
+
+ outputUri = Expander.expand( template, match.getParams(), evaluator );
+ assertNotNull(outputUri.toString());
+ assertThat(
+ outputUri.toString(),
+ is( "https://gateway-host:9443/gateway/default/foo/context/?user.name=admin#/login" ) );
+ }
+
+ @Test
+ public void testLiteralsAndRegexInTemplates() throws Exception {
+ String output;
+ Matcher<Void> matcher;
+ Matcher<Void>.Match match;
+ Template input, template, rewrite;
+ Evaluator evaluator;
+
+ evaluator = new Evaluator() {
+ @Override
+ public List<String> evaluate( String function, List<String> parameters ) {
+ return Arrays.asList( "https://gateway-host:9443/gateway/default" );
+ }
+ };
+
+ // Check to make sure that you can use constants within the {}
+ template = Parser.parseTemplate( "{root=ROOT}/{path=**}" );
+ rewrite = Parser.parseTemplate( "{root}/{path}" );
+ matcher = new Matcher<Void>();
+ matcher.add( template, null );
+ input = Parser.parseLiteral( "ROOT/child/path" );
+ match = matcher.match( input );
+ assertThat( match, notNullValue() );
+ output = Expander.expandToString( rewrite, match.getParams(), evaluator );
+ assertThat( output, is( "ROOT/child/path" ) );
+
+ // Check to see what happens when you use the special { character within the {}.
+ template = Parser.parseTemplate( "{root={}/{path=**}" );
+ rewrite = Parser.parseTemplate( "{root}/{path}" );
+ matcher = new Matcher<Void>();
+ matcher.add( template, null );
+ input = Parser.parseLiteral( "{/child/path" );
+ match = matcher.match( input );
+ assertThat( match, notNullValue() );
+ output = Expander.expandToString( rewrite, match.getParams(), evaluator );
+ assertThat( output, is( "{/child/path" ) );
+
+ // Check to see what happens when you use the special } character within the {}.
+ template = Parser.parseTemplate( "{root=}}/{path=**}" );
+ rewrite = Parser.parseTemplate( "{root}/{path}" );
+ matcher = new Matcher<Void>();
+ matcher.add( template, null );
+ input = Parser.parseLiteral( "}/child/path" );
+ match = matcher.match( input );
+ assertThat( match, notNullValue() );
+ output = Expander.expandToString( rewrite, match.getParams(), evaluator );
+ assertThat( output, is( "}/child/path" ) );
+
+ // Check to see what happens when you use the special } character within the {}.
+ template = Parser.parseTemplate( "{root={}}/{path=**}" );
+ rewrite = Parser.parseTemplate( "{root}/{path}" );
+ matcher = new Matcher<Void>();
+ matcher.add( template, null );
+ input = Parser.parseLiteral( "{}/child/path" );
+ match = matcher.match( input );
+ assertThat( match, notNullValue() );
+ output = Expander.expandToString( rewrite, match.getParams(), evaluator );
+ assertThat( output, is( "{}/child/path" ) );
+
+ template = Parser.parseTemplate( "{var=${*}}/{path=**}" );
+ rewrite = Parser.parseTemplate( "{var}/{path}" );
+
+ matcher = new Matcher<Void>();
+ matcher.add( template, null );
+
+ input = Parser.parseLiteral( "${app.dir}/child/path" );
+ match = matcher.match( input );
+ assertThat( match, notNullValue() );
+
+ output = Expander.expandToString( rewrite, match.getParams(), evaluator );
+ assertThat( output, is( "${app.dir}/child/path" ) );
+ }
+
+}