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" ) );
 +  }
 +
 +}