You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2014/06/19 16:44:19 UTC

git commit: KNOX-395 POC for Jersey Topology Service from Knox

Repository: knox
Updated Branches:
  refs/heads/master 10de69b67 -> 4ca54c5a1


KNOX-395 POC for Jersey Topology Service from Knox

Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/4ca54c5a
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/4ca54c5a
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/4ca54c5a

Branch: refs/heads/master
Commit: 4ca54c5a13e7bbf600a483cdebd653e8adcbc2a9
Parents: 10de69b
Author: Larry McCay <lm...@hortonworks.com>
Authored: Thu Jun 19 10:44:01 2014 -0400
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Thu Jun 19 10:44:01 2014 -0400

----------------------------------------------------------------------
 .../JerseyServiceDeploymentContributorBase.java |   6 +-
 gateway-service-vault/pom.xml                   |  59 ++
 .../service/vault/CredentialResource.java       | 128 +++
 .../VaultServiceDeploymentContributor.java      |  60 ++
 ....gateway.deploy.ServiceDeploymentContributor |  19 +
 gateway-test/pom.xml                            |   5 +
 .../util/urltemplate/MatcherTest.java.orig      | 839 +++++++++++++++++++
 pom.xml                                         |  10 +-
 8 files changed, 1123 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-provider-jersey/src/main/java/org/apache/hadoop/gateway/jersey/JerseyServiceDeploymentContributorBase.java
----------------------------------------------------------------------
diff --git a/gateway-provider-jersey/src/main/java/org/apache/hadoop/gateway/jersey/JerseyServiceDeploymentContributorBase.java b/gateway-provider-jersey/src/main/java/org/apache/hadoop/gateway/jersey/JerseyServiceDeploymentContributorBase.java
index fb7ac39..dc8a340 100644
--- a/gateway-provider-jersey/src/main/java/org/apache/hadoop/gateway/jersey/JerseyServiceDeploymentContributorBase.java
+++ b/gateway-provider-jersey/src/main/java/org/apache/hadoop/gateway/jersey/JerseyServiceDeploymentContributorBase.java
@@ -30,6 +30,7 @@ import java.util.List;
 public abstract class JerseyServiceDeploymentContributorBase extends ServiceDeploymentContributorBase {
 
   private static final String PACKAGES_PARAM = "jersey.config.server.provider.packages";
+  private static final String TRACE_LOGGING_PARAM = "jersey.config.server.tracing";
 
   protected abstract String[] getPackages();
 
@@ -50,8 +51,11 @@ public abstract class JerseyServiceDeploymentContributorBase extends ServiceDepl
       param.name( PACKAGES_PARAM );
       param.value( packages );
       params.add( param );
+//      FilterParamDescriptor trace = resource.createFilterParam();
+//      param.name( TRACE_LOGGING_PARAM );
+//      param.value( "ALL" );
+//      params.add( trace );
       context.contributeFilter( service, resource, "pivot", "jersey", params );
     }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-service-vault/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-service-vault/pom.xml b/gateway-service-vault/pom.xml
new file mode 100644
index 0000000..9dab0dd
--- /dev/null
+++ b/gateway-service-vault/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.hadoop</groupId>
+    <artifactId>gateway</artifactId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <groupId>org.apache.hadoop</groupId>
+  <artifactId>gateway-service-vault</artifactId>
+  <version>0.5.0-SNAPSHOT</version>
+  <name>gateway-service-vault</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  <dependencies>
+	  <dependency>
+	    <groupId>${gateway-group}</groupId>
+	    <artifactId>gateway-spi</artifactId>
+	  </dependency>
+	  <dependency>
+	    <groupId>${gateway-group}</groupId>
+	    <artifactId>gateway-provider-rewrite</artifactId>
+	  </dependency>
+	  <dependency>
+	    <groupId>${gateway-group}</groupId>
+	    <artifactId>gateway-provider-jersey</artifactId>
+	  </dependency>
+		<dependency>
+		  <groupId>com.owlike</groupId>
+		  <artifactId>genson</artifactId>
+		  <version>0.99</version>
+		</dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/CredentialResource.java
----------------------------------------------------------------------
diff --git a/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/CredentialResource.java b/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/CredentialResource.java
new file mode 100644
index 0000000..9f60624
--- /dev/null
+++ b/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/CredentialResource.java
@@ -0,0 +1,128 @@
+/**
+ * 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.hadoop.gateway.service.vault;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.AliasService;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.serverError;
+import static javax.ws.rs.core.Response.status;
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+
+@Path( "/vault/credentials" )
+public class CredentialResource {
+  @Context 
+  private HttpServletRequest request;
+  
+  @GET
+  @Path("{alias}")
+  @Produces({APPLICATION_JSON, APPLICATION_XML})
+  public Response getCredential(@PathParam("alias") String alias) {
+    if (alias != null && !alias.isEmpty()) {
+      CredentialValue value = getCredentialValueForAlias(alias);
+      if (value != null) {
+          return ok(value).build();
+      } else {
+          return status(NOT_FOUND).build();
+      }
+    } else {
+        return status(BAD_REQUEST).
+            entity("Please provide a credential alias in the path").
+              type(TEXT_PLAIN_TYPE).build();
+    }
+  }
+
+  @GET
+  @Produces({APPLICATION_JSON, APPLICATION_XML})
+  public Response getCredentials() {
+    List<String> aliases = getCredentialsList();
+    if (aliases != null) {
+        return ok(aliases).build();
+    } else {
+        return status(NOT_FOUND).build();
+    }
+  }
+
+  /**
+   * @return
+   */
+  private List<String> getCredentialsList() {
+    GatewayServices services = (GatewayServices)request.getServletContext().
+        getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+    String clusterName = (String) request.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+    AliasService as = services.getService(GatewayServices.ALIAS_SERVICE);
+    List<String> aliases = as.getAliasesForCluster(clusterName);
+    return aliases;
+  }
+
+  /**
+   * @param alias
+   * @return
+   */
+  private CredentialValue getCredentialValueForAlias(String alias) {
+    GatewayServices services = (GatewayServices)request.getServletContext().
+        getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+    String clusterName = (String) request.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+    AliasService as = services.getService(GatewayServices.ALIAS_SERVICE);
+    char[] credential = as.getPasswordFromAliasForCluster(clusterName, alias);
+    if (credential != null) {
+      return new CredentialValue(alias, new String(credential));
+    }
+    return null;
+  }
+  
+  public static class CredentialValue {
+    private String alias;
+    private String credential;
+    
+    public CredentialValue(String alias, String credential) {
+      super();
+      this.alias = alias;
+      this.credential = credential;
+    }
+    
+    public String getAlias() {
+      return alias;
+    }
+    public void setAlias(String alias) {
+      this.alias = alias;
+    }
+    public String getCredential() {
+      return credential;
+    }
+    public void setCredential(String credential) {
+      this.credential = credential;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/deploy/VaultServiceDeploymentContributor.java
----------------------------------------------------------------------
diff --git a/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/deploy/VaultServiceDeploymentContributor.java b/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/deploy/VaultServiceDeploymentContributor.java
new file mode 100644
index 0000000..b834cb4
--- /dev/null
+++ b/gateway-service-vault/src/main/java/org/apache/hadoop/gateway/service/vault/deploy/VaultServiceDeploymentContributor.java
@@ -0,0 +1,60 @@
+/**
+ * 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.hadoop.gateway.service.vault.deploy;
+
+import org.apache.hadoop.gateway.jersey.JerseyServiceDeploymentContributorBase;
+
+/**
+ *
+ */
+public class VaultServiceDeploymentContributor extends JerseyServiceDeploymentContributorBase {
+
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor#getRole()
+   */
+  @Override
+  public String getRole() {
+    // TODO Auto-generated method stub
+    return "VAULT";
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor#getName()
+   */
+  @Override
+  public String getName() {
+    return "KnoxVaultService";
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.jersey.JerseyServiceDeploymentContributorBase#getPackages()
+   */
+  @Override
+  protected String[] getPackages() {
+    return new String[]{ "org.apache.hadoop.gateway.service.vault" };
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.jersey.JerseyServiceDeploymentContributorBase#getPatterns()
+   */
+  @Override
+  protected String[] getPatterns() {
+    return new String[]{ "vault/**?**" };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-service-vault/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor
----------------------------------------------------------------------
diff --git a/gateway-service-vault/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor b/gateway-service-vault/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor
new file mode 100644
index 0000000..fc73269
--- /dev/null
+++ b/gateway-service-vault/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ServiceDeploymentContributor
@@ -0,0 +1,19 @@
+##########################################################################
+# 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.
+##########################################################################
+
+org.apache.hadoop.gateway.service.vault.deploy.VaultServiceDeploymentContributor
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-test/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-test/pom.xml b/gateway-test/pom.xml
index 736b66d..ba7d86d 100644
--- a/gateway-test/pom.xml
+++ b/gateway-test/pom.xml
@@ -40,6 +40,11 @@
             <artifactId>gateway-provider-jersey</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-service-vault</artifactId>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>${gateway-group}</groupId>

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/gateway-util-urltemplate/src/test/java/org/apache/hadoop/gateway/util/urltemplate/MatcherTest.java.orig
----------------------------------------------------------------------
diff --git a/gateway-util-urltemplate/src/test/java/org/apache/hadoop/gateway/util/urltemplate/MatcherTest.java.orig b/gateway-util-urltemplate/src/test/java/org/apache/hadoop/gateway/util/urltemplate/MatcherTest.java.orig
new file mode 100644
index 0000000..6b6012b
--- /dev/null
+++ b/gateway-util-urltemplate/src/test/java/org/apache/hadoop/gateway/util/urltemplate/MatcherTest.java.orig
@@ -0,0 +1,839 @@
+/**
+ * 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.hadoop.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.URISyntaxException;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+
+//TODO: Test to make sure that extra unmatched query parameters prevent a match.
+@Category( { UnitTests.class, FastTests.class } )
+public class MatcherTest {
+
+  private void addTemplate( Matcher<String> matcher, String template ) throws URISyntaxException {
+    matcher.add( Parser.parse( template ), template );
+  }
+
+  private void assertValidMatch( Matcher<String> matcher, String uri, String template ) throws URISyntaxException {
+    if( template == null ) {
+      assertThat( matcher.match( Parser.parse( uri ) ), nullValue() );
+    } else {
+      Template uriTemplate = Parser.parse( uri );
+      Matcher<String>.Match match = matcher.match( uriTemplate );
+      assertThat( "Expected to find a match.", match, notNullValue() );
+      assertThat( match.getValue(), equalTo( template ) );
+    }
+  }
+
+  @Test
+  public void testWildcardCharacterInInputTemplate() throws URISyntaxException {
+    Matcher<String> matcher;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    // First verify that if .../test_table/test_row/family1... works.
+    matcher = new Matcher<String>();
+    inputTemplate = Parser.parse( "https://localhost:8443/gateway/sandbox/hbase/test_table/test_row/family1:row2_col1,family2/0,9223372036854775807?v=1" );
+    patternTemplate = Parser.parse( "*://*:*/**/webhdfs/{version}/{path=**}?{**}" );
+    matcher.add( patternTemplate, "webhdfs" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+
+    // Then reproduce the issue with .../test_table/*/family1..
+    matcher = new Matcher<String>();
+    inputTemplate = Parser.parse( "https://localhost:8443/gateway/sandbox/hbase/test_table/*/family1:row2_col1,family2/0,9223372036854775807?v=1" );
+    patternTemplate = Parser.parse( "*://*:*/**/webhdfs/{version}/{path=**}?{**}" );
+    matcher.add( patternTemplate, "webhdfs" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+
+    // Reproduce the issue where the wrong match was picked when there was a "*" in the input URL template.
+    matcher = new Matcher<String>();
+    inputTemplate = Parser.parse( "https://localhost:8443/gateway/sandbox/hbase/test_table/*/family1:row2_col1,family2/0,9223372036854775807?v=1" );
+    patternTemplate = Parser.parse( "*://*:*/**/webhdfs/{version}/{path=**}?{**}" );
+    matcher.add( patternTemplate, "webhdfs" );
+    patternTemplate = Parser.parse( "*://*:*/**/hbase/{path=**}?{**}" );
+    matcher.add( patternTemplate, "hbase" );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getValue(), is( "hbase" ) );
+  }
+  
+  @Test
+  public void testDefaultAppDeployment() throws Exception {
+    Matcher<String> matcher;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    matcher = new Matcher<String>();
+    inputTemplate = Parser.parse( "https://localhost:8443/webhdfs/v1/tmp?op=LISTSTATUS" );
+    patternTemplate = Parser.parse( "*://*:*/webhdfs/{version}/{path=**}?{**}" );
+    matcher.add( patternTemplate, "webhdfs" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+  }
+
+  @Test
+  public void testRootPathMatching() throws Exception {
+    Matcher<String> matcher;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*/" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*/*" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*/**" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+//KM: I'm not sure what the correct behavior is here.
+//    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+//    match = matcher.match( inputTemplate );
+//    assertThat( match, ? );
+//    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+//    match = matcher.match( inputTemplate );
+//    assertThat( match, ? );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*/{path=*}" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+
+    ///////
+    patternTemplate = Parser.parse( "*://*:*/{path=**}" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-match" );
+
+//KM: I'm not sure what the correct behavior is here.
+//    inputTemplate = Parser.parse( "test-scheme://test-host:42" );
+//    match = matcher.match( inputTemplate );
+//    assertThat( match, ? );
+//    inputTemplate = Parser.parse( "test-scheme://test-host:42/" );
+//    match = matcher.match( inputTemplate );
+//    assertThat( match, ? );
+    inputTemplate = Parser.parse( "test-scheme://test-host:42/test-path" );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+  }
+
+  @Test
+  public void testTopLevelPathGlobMatch() throws Exception {
+    Matcher<String> matcher;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    patternTemplate = Parser.parse( "{*}://{host}:{*}/{**=**}?{**}" );
+    inputTemplate = Parser.parse( "test-scheme://test-input-host:42/test-path/test-file?test-name=test-value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-math" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the path ** should include both test-path and test-file", match, notNullValue() );
+
+    patternTemplate = Parser.parse( "{*}://{host}:{*}/{**}?{**}" );
+    inputTemplate = Parser.parse( "test-scheme://test-input-host:42/test-path/test-file?test-name=test-value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "test-math" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the path ** should include both test-path and test-file", match, notNullValue() );
+  }
+
+  @Test
+  public void testQueryHandling() throws Exception {
+    Matcher<String> matcher;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    patternTemplate = Parser.parse( "/path?{query}" );
+    inputTemplate = Parser.parse( "/path" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should not match because input does not contain the required query.", match, nullValue() );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/path?{query}" ), "T1" );
+    matcher.add( Parser.parse( "/path" ), "T2" );
+    inputTemplate = Parser.parse( "/path" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because there is an entry in the matcher without a query.", match, notNullValue() );
+    assertThat( match.getValue(), equalTo( "T2") );
+
+    patternTemplate = Parser.parse( "/path?{query}" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because input does contain the required query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value" ) );
+    assertThat( match.getParams().resolve( "query" ).size(), equalTo( 1 ) );
+
+    patternTemplate = Parser.parse( "/path?{*}" );
+    inputTemplate = Parser.parse( "/path" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should not match because input does not contain the required query.", match, nullValue() );
+
+    patternTemplate = Parser.parse( "/path?*" );
+    inputTemplate = Parser.parse( "/path" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should not match because input does not contain the required query.", match, nullValue() );
+
+    patternTemplate = Parser.parse( "/path?*" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat(
+        "Should match because the template has an extra query and the input has a query.",
+        match, notNullValue() );
+    assertThat(
+        "Should not have extracts any parameters since pattern template didn't contain {}",
+        match.getParams().resolve( "query" ), nullValue() );
+
+    patternTemplate = Parser.parse( "/path?{*}" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because input does contain the required query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value" ) );
+
+    patternTemplate = Parser.parse( "/path?{**}" );
+    inputTemplate = Parser.parse( "/path" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the template has an optional query.", match, notNullValue() );
+
+    patternTemplate = Parser.parse( "/path?**" );
+    inputTemplate = Parser.parse( "/path" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the template has an optional extra query.", match, notNullValue() );
+
+    patternTemplate = Parser.parse( "/path?**" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the template has an optional extra query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), nullValue() );
+
+    patternTemplate = Parser.parse( "/path?{**}" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because the template has an optional extra query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value" ) );
+    assertThat( match.getParams().resolve( "query" ).size(), equalTo( 1 ) );
+
+    patternTemplate = Parser.parse( "/path?{query}&{*}" );
+    inputTemplate = Parser.parse( "/path?query=value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should not match because input does not contain the required extra query.", match, nullValue() );
+
+    patternTemplate = Parser.parse( "/path?{query}&{*}" );
+    inputTemplate = Parser.parse( "/path?query=value&extra=extra-value" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because input does contain the required query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value" ) );
+    assertThat( match.getParams().resolve( "query" ).size(), equalTo( 1 ) );
+
+    patternTemplate = Parser.parse( "/path?{query=**}" );
+    inputTemplate = Parser.parse( "/path?query=value1&query=value2" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because input does contain the required query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value1" ) );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value2" ) );
+    assertThat( match.getParams().resolve( "query" ).size(), equalTo( 2 ) );
+
+    patternTemplate = Parser.parse( "/path?{query}" );
+    inputTemplate = Parser.parse( "/path?query=value1&query=value2" );
+    matcher = new Matcher<String>();
+    matcher.add( patternTemplate, "T" );
+    match = matcher.match( inputTemplate );
+    assertThat( "Should match because input does contain the required query.", match, notNullValue() );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value1" ) );
+    assertThat( match.getParams().resolve( "query" ), hasItem( "value2" ) );
+    assertThat( match.getParams().resolve( "query" ).size(), equalTo( 2 ) );
+  }
+
+  @Test
+  public void testMatchCompleteUrl() throws Exception {
+    Matcher<String> matcher;
+    String pattern, input;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    matcher = new Matcher<String>();
+    pattern = "foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+
+    input = pattern;
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match, notNullValue() );
+
+    input = "not://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match, nullValue() );
+  }
+
+  @Test
+  public void testMatch() throws Exception {
+    Matcher<String> matcher;
+    String pattern, input;
+    Template patternTemplate, inputTemplate;
+    Matcher<String>.Match match;
+
+    matcher = new Matcher<String>();
+    pattern = "path";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    assertThat( matcher.get( patternTemplate ), is( pattern ) );
+    input = "path";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+
+    matcher = new Matcher<String>();
+    pattern = "/path";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "/path";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "path/path";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "path/path";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "*/path";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "pathA/path";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "**/path";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "pathA/pathB/path";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "path-1/{path=**}/path-4";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "path-1/path-2/path-3/path-4";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+    assertThat( match.getParams().resolve( "path" ).get( 0 ), equalTo( "path-2" ) );
+    assertThat( match.getParams().resolve( "path" ).get( 1 ), equalTo( "path-3" ) );
+
+    matcher = new Matcher<String>();
+    pattern = "/";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "/";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+
+    matcher = new Matcher<String>();
+    pattern = "";
+    patternTemplate = Parser.parse( pattern );
+    matcher.add( patternTemplate, pattern );
+    input = "";
+    inputTemplate = Parser.parse( input );
+    match = matcher.match( inputTemplate );
+    assertThat( match.getTemplate(), sameInstance( patternTemplate ) );
+    assertThat( match.getValue(), equalTo( pattern ) );
+  }
+
+  @Test
+  public void testVariousPatterns() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs" ), "/webhdfs" );
+    matcher.add( Parser.parse( "/webhdfs/dfshealth.jsp" ), "/webhdfs/dfshealth.jsp" );
+    matcher.add( Parser.parse( "/webhdfs/*.jsp" ), "/webhdfs/*.jsp" );
+    matcher.add( Parser.parse( "/webhdfs/other.jsp" ), "/webhdfs/other.jsp" );
+    matcher.add( Parser.parse( "/webhdfs/*" ), "/webhdfs/*" );
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    matcher.add( Parser.parse( "/webhdfs/v1/**" ), "/webhdfs/v1/**" );
+    matcher.add( Parser.parse( "/webhdfs/**/middle/*.xml" ), "/webhdfs/**/middle/*.xml" );
+
+    assertValidMatch( matcher, "/webhdfs", "/webhdfs" );
+    assertValidMatch( matcher, "/webhdfs/dfshealth.jsp", "/webhdfs/dfshealth.jsp" );
+    assertValidMatch( matcher, "/webhdfs/v1", "/webhdfs/*" ); // The star should be picked in preference to the glob.
+    assertValidMatch( matcher, "/webhdfs/some.jsp", "/webhdfs/*.jsp" );
+    assertValidMatch( matcher, "/webhdfs/other.jsp", "/webhdfs/other.jsp" );
+    assertValidMatch( matcher, "/webhdfs/path/some.jsp", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/path/middle/some.jsp", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/path/middle/some.xml", "/webhdfs/**/middle/*.xml" );
+    assertValidMatch( matcher, "/webhdfs/path/to/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/v1/path/to/file", "/webhdfs/v1/**" );
+  }
+
+  @Test
+  public void testStar() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/*" ), "/webhdfs/*" );
+    assertValidMatch( matcher, "/webhdfs/*", "/webhdfs/*" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/*" );
+    assertValidMatch( matcher, "/webhdfs/path/", "/webhdfs/*" );
+    assertValidMatch( matcher, "/webhdfs/path/file", null );
+    assertValidMatch( matcher, "/webhdfs/path/path/", null );
+  }
+
+  @Test
+  public void testGlob() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/path/", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/path/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/path/path/", "/webhdfs/**" );
+  }
+
+  @Test
+  public void testMatrixParam() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    matcher.add( Parser.parse( "/webhdfs/browseDirectory.jsp;dn=*" ), "/webhdfs/browseDirectory.jsp;dn=*" );
+    assertValidMatch( matcher, "/webhdfs/browseDirectory.jsp;dn=X", "/webhdfs/browseDirectory.jsp;dn=*" );
+  }
+
+  @Test
+  public void testTwoGlobsAtDifferentDepths() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    matcher.add( Parser.parse( "/webhdfs/v1/**" ), "/webhdfs/v1/**" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/v1/file", "/webhdfs/v1/**" );
+
+    // Reverse the put order.
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/v1/**" ), "/webhdfs/v1/**" );
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/v1/file", "/webhdfs/v1/**" );
+  }
+
+  @Test
+  public void testGlobsVsStarsAtSameDepth() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/*" ), "/webhdfs/*" );
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/*" ); // The star should be picked in preference to the glob.
+    assertValidMatch( matcher, "/webhdfs/path/file", "/webhdfs/**" );
+
+    // Reverse the put order.
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/webhdfs/**" ), "/webhdfs/**" );
+    matcher.add( Parser.parse( "/webhdfs/*" ), "/webhdfs/*" );
+    assertValidMatch( matcher, "/webhdfs/path/file", "/webhdfs/**" );
+    assertValidMatch( matcher, "/webhdfs/file", "/webhdfs/*" );
+  }
+
+  @Test
+  public void testMatchingPatternsWithinPathSegments() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/path/{file}" ), "default" );
+    assertValidMatch( matcher, "/path/file-name", "default" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/path/{file=*}" ), "*" );
+    assertValidMatch( matcher, "/path/some-name", "*" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/path/{more=**}" ), "**" );
+    assertValidMatch( matcher, "/path/some-path/some-name", "**" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "/path/{regex=prefix*suffix}" ), "regex" );
+    assertValidMatch( matcher, "/path/prefix-middle-suffix", "regex" );
+    assertValidMatch( matcher, "/path/not-prefix-middle-suffix", null );
+  }
+
+  @Test
+  public void testMatchingPatternsWithinQuerySegments() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "?query={queryParam}" ), "default" );
+    assertValidMatch( matcher, "?query=value", "default" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "?query={queryParam=*}" ), "*" );
+    assertValidMatch( matcher, "?query=some-value", "*" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "?query={queryParam=**}" ), "**" );
+    assertValidMatch( matcher, "?query=some-value", "**" );
+
+    matcher = new Matcher<String>();
+    matcher.add( Parser.parse( "?query={queryParam=prefix*suffix}" ), "regex" );
+    assertValidMatch( matcher, "?query=prefix-middle-suffix", "regex" );
+    assertValidMatch( matcher, "?query=not-prefix-middle-suffix", null );
+  }
+
+  @Test
+  public void testMatchingForTemplatesThatVaryOnlyByQueryParams() throws URISyntaxException {
+    Matcher<String> matcher = new Matcher<String>();
+    addTemplate( matcher, "?one={queryParam}" );
+    addTemplate( matcher, "?two={queryParam}" );
+
+    assertValidMatch( matcher, "?one=value", "?one={queryParam}" );
+    assertValidMatch( matcher, "?two=value", "?two={queryParam}" );
+    assertValidMatch( matcher, "?three=value", null );
+    assertValidMatch( matcher, "?", null );
+  }
+
+  @Test
+  public void testFullUrlExtraction() throws URISyntaxException {
+    Template template;
+    Template input;
+    Matcher<?> matcher;
+    Matcher<?>.Match match;
+    Params params;
+
+    template = Parser.parse( "{scheme}://{username}:{password}@{host}:{port}/{root}/{path}/{file}?queryA={paramA}&queryB={paramB}#{fragment}" );
+    input = Parser.parse( "http://horton:hadoop@hortonworks.com:80/top/middle/end?queryA=valueA&queryB=valueB#section" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+
+    assertThat( params.getNames(), hasItem( "scheme" ) );
+    assertThat( params.resolve( "scheme" ), hasItem( "http" ) );
+    assertThat( params.getNames(), hasItem( "username" ) );
+    assertThat( params.resolve( "username" ), hasItem( "horton" ) );
+    assertThat( params.getNames(), hasItem( "password" ) );
+    assertThat( params.resolve( "password" ), hasItem( "hadoop" ) );
+    assertThat( params.getNames(), hasItem( "host" ) );
+    assertThat( params.resolve( "host" ), hasItem( "hortonworks.com" ) );
+    assertThat( params.getNames(), hasItem( "port" ) );
+    assertThat( params.resolve( "port" ), hasItem( "80" ) );
+    assertThat( params.getNames(), hasItem( "root" ) );
+    assertThat( params.resolve( "root" ), hasItem( "top" ) );
+    assertThat( params.getNames(), hasItem( "path" ) );
+    assertThat( params.resolve( "path" ), hasItem( "middle" ) );
+    assertThat( params.getNames(), hasItem( "file" ) );
+    assertThat( params.resolve( "file" ), hasItem( "end" ) );
+    assertThat( params.getNames(), hasItem( "paramA" ) );
+    assertThat( params.resolve( "paramA" ), hasItem( "valueA" ) );
+    assertThat( params.getNames(), hasItem( "paramB" ) );
+    assertThat( params.resolve( "paramB" ), hasItem( "valueB" ) );
+    assertThat( params.getNames(), hasItem( "fragment" ) );
+    assertThat( params.resolve( "fragment" ), hasItem( "section" ) );
+    assertThat( params.getNames().size(), equalTo( 11 ) );
+  }
+
+  @Test
+  public void testMultipleDoubleStarPathMatching() throws URISyntaxException {
+    Template template;
+    Template input;
+    Matcher<?> matcher;
+    Matcher<String> stringMatcher;
+    Matcher<?>.Match match;
+
+//    template = Parser.parse( "*://*:*/**/webhdfs/v1/**?**" );
+//    input = Parser.parse( "http://localhost:53221/gateway/cluster/webhdfs/v1/tmp/GatewayWebHdfsFuncTest/testBasicHdfsUseCase/dir?user.name=hdfs&op=MKDIRS" );
+//    matcher = new Matcher<String>( template, "test-value" );
+//    match = matcher.match( input );
+//    assertThat( (String)match.getValue(), is( "test-value" ) );
+//
+//    template = Parser.parse( "*://*:*/**/webhdfs/v1/{path=**}?{**=*}" );
+//    input = Parser.parse( "http://localhost:53221/gateway/cluster/webhdfs/v1/tmp/GatewayWebHdfsFuncTest/testBasicHdfsUseCase/dir?user.name=hdfs&op=MKDIRS" );
+//    matcher = new Matcher<String>( template, "test-value-2" );
+//    match = matcher.match( input );
+//    assertThat( (String)match.getValue(), is( "test-value-2" ) );
+//
+//    stringMatcher = new Matcher<String>();
+//    template = Parser.parse( "*://*:*/**/webhdfs/data/v1/{path=**}?host={host=*}&port={port=*}&{**=*}" );
+//    stringMatcher.add( template, "test-value-C" );
+//    template = Parser.parse( "*://*:*/**/webhdfs/v1/{path=**}?{**=*}" );
+//    stringMatcher.add( template, "test-value-B" );
+//    input = Parser.parse( "http://localhost:53221/gateway/cluster/webhdfs/v1/tmp/GatewayWebHdfsFuncTest/testBasicHdfsUseCase/dir?user.name=hdfs&op=MKDIRS" );
+//    match = stringMatcher.match( input );
+//    assertThat( match.getValue(), notNullValue() );
+//    assertThat( (String)match.getValue(), is( "test-value-B" ) );
+
+    // This is just a reverse of the above.  The order caused a bug.
+    stringMatcher = new Matcher<String>();
+    template = Parser.parse( "*://*:*/**/webhdfs/v1/{path=**}?{**=*}" );
+    stringMatcher.add( template, "test-value-B" );
+    template = Parser.parse( "*://*:*/**/webhdfs/data/v1/{path=**}?host={host=*}&port={port=*}&{**=*}" );
+    stringMatcher.add( template, "test-value-C" );
+    input = Parser.parse( "http://localhost:53221/gateway/cluster/webhdfs/v1/tmp/GatewayWebHdfsFuncTest/testBasicHdfsUseCase/dir?user.name=hdfs&op=MKDIRS" );
+    match = stringMatcher.match( input );
+    assertThat( match.getValue(), notNullValue() );
+    assertThat( (String)match.getValue(), is( "test-value-B" ) );
+
+  }
+
+  @Test
+  public void testPathExtraction() throws Exception {
+    Template template;
+    Template input;
+    Matcher<?> matcher;
+    Matcher<?>.Match match;
+    Params params;
+
+    template = Parser.parse( "{path-queryParam}" );
+    input = Parser.parse( "path-value" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "path-queryParam" ) );
+    assertThat( params.resolve( "path-queryParam" ).size(), equalTo( 1 ) );
+    assertThat( params.resolve( "path-queryParam" ), hasItem( "path-value" ) );
+
+    template = Parser.parse( "/some-path/{path-queryParam}" );
+    input = Parser.parse( "/some-path/path-value" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "path-queryParam" ) );
+    assertThat( params.resolve( "path-queryParam" ).size(), equalTo( 1 ) );
+    assertThat( params.resolve( "path-queryParam" ), hasItem( "path-value" ) );
+
+    template = Parser.parse( "/some-path/{path-queryParam}/some-other-path" );
+    input = Parser.parse( "/some-path/path-value/some-other-path" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "path-queryParam" ) );
+    assertThat( params.resolve( "path-queryParam" ).size(), equalTo( 1 ) );
+    assertThat( params.resolve( "path-queryParam" ), hasItem( "path-value" ) );
+
+    template = Parser.parse( "{path=**}" );
+    input = Parser.parse( "A/B" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "path" ) );
+    assertThat( params.resolve( "path" ).size(), equalTo( 2 ) );
+    assertThat( params.resolve( "path" ), hasItem( "A" ) );
+    assertThat( params.resolve( "path" ), hasItem( "B" ) );
+
+    template = Parser.parse( "/top/{mid=**}/end" );
+    input = Parser.parse( "/top/A/B/end" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "mid" ) );
+    assertThat( params.resolve( "mid" ).size(), equalTo( 2 ) );
+    assertThat( params.resolve( "mid" ), hasItem( "A" ) );
+    assertThat( params.resolve( "mid" ), hasItem( "B" ) );
+
+    template = Parser.parse( "*://*:*/{path=**}?{**}" );
+    input = Parser.parse( "http://host:port/pathA/pathB" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params.resolve( "path" ), hasItem( "pathA" ) );
+    assertThat( params.resolve( "path" ), hasItem( "pathB" ) );
+    assertThat( params.resolve( "path" ).size(), is( 2 ) );
+
+    template = Parser.parse( "*://*:*/{path=**}?{**}" );
+    input = Parser.parse( "http://host:port/pathA/pathB" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params.resolve( "path" ), hasItem( "pathA" ) );
+    assertThat( params.resolve( "path" ), hasItem( "pathB" ) );
+    assertThat( params.resolve( "path" ).size(), is( 2 ) );
+
+    template = Parser.parse( "*://*:*/{path=**}?{**}" );
+    input = Parser.parse( "http://host:port/pathA/pathB" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params.resolve( "path" ), hasItem( "pathA" ) );
+    assertThat( params.resolve( "path" ), hasItem( "pathB" ) );
+    assertThat( params.resolve( "path" ).size(), is( 2 ) );
+  }
+
+  @Test
+  public void testQueryExtraction() throws Exception {
+    Template template;
+    Template input;
+    Matcher<?> matcher;
+    Matcher<?>.Match match;
+    Params params;
+
+    template = Parser.parse( "?query-queryParam={queryParam-name}" );
+    input = Parser.parse( "?query-queryParam=queryParam-value" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "queryParam-name" ) );
+    assertThat( params.resolve( "queryParam-name" ).size(), equalTo( 1 ) );
+    assertThat( params.resolve( "queryParam-name" ), hasItem( "queryParam-value" ) );
+
+    template = Parser.parse( "?query-queryParam={queryParam-name}" );
+    input = Parser.parse( "?query-queryParam=queryParam-value" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 1 ) );
+    assertThat( params.getNames(), hasItem( "queryParam-name" ) );
+    assertThat( params.resolve( "queryParam-name" ).size(), equalTo( 1 ) );
+    assertThat( params.resolve( "queryParam-name" ), hasItem( "queryParam-value" ) );
+  }
+
+  @Test
+  public void testEdgeCaseExtraction() throws Exception {
+    Template template;
+    Template input;
+    Matcher<?> matcher;
+    Matcher<?>.Match match;
+    Params params;
+
+    template = Parser.parse( "" );
+    input = Parser.parse( "" );
+    matcher = new Matcher<Void>( template, null );
+    match = matcher.match( input );
+    params = match.getParams();
+    assertThat( params, notNullValue() );
+    assertThat( params.getNames().size(), equalTo( 0 ) );
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/4ca54c5a/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d99c952..d7281ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,7 @@
         <module>gateway-release</module>
         <module>gateway-test</module>
         <module>hsso-release</module>
+        <module>gateway-service-vault</module>
     </modules>
 
     <properties>
@@ -425,6 +426,11 @@
             </dependency>
             <dependency>
                 <groupId>${gateway-group}</groupId>
+                <artifactId>gateway-service-vault</artifactId>
+                <version>${gateway-version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${gateway-group}</groupId>
                 <artifactId>gateway-service-as</artifactId>
                 <version>${gateway-version}</version>
             </dependency>
@@ -497,12 +503,12 @@
             <dependency>
                 <groupId>org.glassfish.jersey.containers</groupId>
                 <artifactId>jersey-container-servlet</artifactId>
-                <version>2.4.1</version>
+                <version>2.6</version>
             </dependency>
             <dependency>
                 <groupId>org.glassfish.jersey.core</groupId>
                 <artifactId>jersey-server</artifactId>
-                <version>2.4</version>
+                <version>2.6</version>
             </dependency>
 
             <dependency>