SENTRY-2367: Implement subsystem to allow for pluggable attribute providers and transports (Brian Towles, reviewed Steve Moist, by Na Li)
diff --git a/pom.xml b/pom.xml
index e31c194..984e15a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -962,6 +962,7 @@
     <module>sentry-tools</module>
     <module>sentry-service</module>
     <module>sentry-dist</module>
+    <module>sentry-spi</module>
   </modules>
 
   <build>
diff --git a/sentry-spi/pom.xml b/sentry-spi/pom.xml
new file mode 100644
index 0000000..f9dbc7f
--- /dev/null
+++ b/sentry-spi/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.sentry</groupId>
+    <artifactId>sentry</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>sentry-spi</artifactId>
+  <name>Sentry Service Provider Interface Control</name>
+  <version>2.1.0-SNAPSHOT</version>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/DefaultProviderLoader.java b/sentry-spi/src/main/java/org/apache/sentry/spi/DefaultProviderLoader.java
new file mode 100644
index 0000000..2d45b46
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/DefaultProviderLoader.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sentry.spi;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+/**
+ * This is the default Provider loader.  It loads from the same classloader as is passed to it.
+ * 
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultProviderLoader implements ProviderLoader {
+
+  private ClassLoader classLoader;
+
+  public DefaultProviderLoader(ClassLoader classLoader) {
+    this.classLoader = classLoader;
+  }
+
+  @Override
+  public List<Spi> loadSpis() {
+    LinkedList<Spi> list = new LinkedList<>();
+    for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
+      list.add(spi);
+    }
+    return list;
+  }
+
+  @Override
+  public List<ProviderFactory> load(Spi spi) {
+    LinkedList<ProviderFactory> list = new LinkedList<>();
+    for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
+      list.add(f);
+    }
+    return list;
+  }
+
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/Provider.java b/sentry-spi/src/main/java/org/apache/sentry/spi/Provider.java
new file mode 100644
index 0000000..9afaed0
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/Provider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * The main Provider interface.
+ * 
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Provider {
+
+
+  default void close() {
+
+  }
+
+
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderFactory.java b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderFactory.java
new file mode 100755
index 0000000..ecc8bf9
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * This is the Factory interface for Providers to implement.  Allows for custom initialization of
+ * Provider instances.
+ * Only one instance of a factory exists per server.
+ *
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderFactory<T extends Provider> {
+
+  T create();
+
+  String getId();
+
+  default int order() {
+    return 0;
+  }
+
+  default void close() {}
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderLoader.java b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderLoader.java
new file mode 100644
index 0000000..eaef3e8
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderLoader.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sentry.spi;
+
+import java.util.List;
+
+/**
+ * This is the ProviderLoader interface to allow for multiple different Provider Loading mechanics.
+ *
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderLoader {
+
+  /**
+   * Load the SPI definitions themselves.
+   *
+   * @return a list of Spi definition objects
+   */
+  List<Spi> loadSpis();
+
+  /**
+   * Load all provider factories of a specific SPI.
+   *
+   * @param spi the Spi definition
+   * @return a list of provider factories
+   */
+  List<ProviderFactory> load(Spi spi);
+
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderManager.java b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderManager.java
new file mode 100644
index 0000000..cf48490
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/ProviderManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.sentry.spi;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+
+
+/**
+ * The ProviderManager that can retrieve instances of ProviderFactory for an SPI.
+ *
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ *
+ */
+@Slf4j
+public class ProviderManager {
+
+  private static ProviderManager instance;
+  private Map<String, Spi> spiMap = new HashMap<>();
+  private List<ProviderLoader> loaders = new LinkedList<ProviderLoader>();
+  private ListMultimap<Class<? extends Provider>, ProviderFactory> cache = ArrayListMultimap
+      .create();
+
+  private ProviderManager(ClassLoader baseClassLoader) {
+    loaders.add(new DefaultProviderLoader(baseClassLoader));
+    loadSpis();
+  }
+
+  public static ProviderManager getInstance() {
+    if (instance == null) {
+      instance = new ProviderManager(ProviderManager.class.getClassLoader());
+    }
+    return instance;
+  }
+
+  private synchronized void loadSpis() {
+    for (ProviderLoader loader : loaders) {
+      List<Spi> spis = loader.loadSpis();
+      if (spis != null) {
+        for (Spi spi : spis) {
+          spiMap.put(spi.getName(), spi);
+        }
+      }
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public synchronized <T extends ProviderFactory> List<T> load(String service) {
+    if (!spiMap.containsKey(service)) {
+      throw new RuntimeException(String.format("SPI Definition for Service %s not found", service));
+    }
+
+    Spi spi = spiMap.get(service);
+    if (!cache.containsKey(spi.getProviderClass())) {
+      LOGGER.debug("Loading Service {}", spi.getName());
+      IdentityHashMap factoryClasses = new IdentityHashMap();
+      for (ProviderLoader loader : loaders) {
+        List<ProviderFactory> f = loader.load(spi);
+        if (f != null) {
+          for (ProviderFactory pf : f) {
+            // make sure there are no duplicates
+            if (!factoryClasses.containsKey(pf.getClass())) {
+              LOGGER.debug("Service {} provider {} loaded", spi.getName(), pf.getId());
+              cache.put(spi.getProviderClass(), pf);
+              factoryClasses.put(pf.getClass(), pf);
+            }
+          }
+        }
+      }
+    }
+    List<T> rtn = (List<T>) cache.get(spi.getProviderClass());
+    return rtn == null ? Collections.EMPTY_LIST : rtn;
+  }
+
+  public synchronized <T extends ProviderFactory> T load(String service, String providerId) {
+    for (ProviderFactory f : load(service)) {
+      if (f.getId().equals(providerId)) {
+        return (T) f;
+      }
+    }
+    return null;
+  }
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/Spi.java b/sentry-spi/src/main/java/org/apache/sentry/spi/Spi.java
new file mode 100644
index 0000000..b0ca50d
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/Spi.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * This is the main Service Provider Interface Definition.  Custom SPIs can be implemented by
+ * implementing this interface and providing a reference to that implementation in the
+ * META-INF/service/org.apache.sentry.spi.Spi file.
+ *
+ * This was borrowed from and inspired by the Keycloak SPI implmentation
+ * http://www.keycloak.org
+ * original Author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Spi {
+
+  String getName();
+
+  Class<? extends Provider> getProviderClass();
+
+  Class<? extends ProviderFactory> getProviderFactoryClass();
+}
diff --git a/sentry-spi/src/main/java/org/apache/sentry/spi/package-info.java b/sentry-spi/src/main/java/org/apache/sentry/spi/package-info.java
new file mode 100644
index 0000000..6379353
--- /dev/null
+++ b/sentry-spi/src/main/java/org/apache/sentry/spi/package-info.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * <p>
+ *   Components to implement a Service Provider Interface implementation for Sentry.
+ * </p>
+ * <p>
+ *   These classes are a way to simplify loading of different "plugin" service implementations
+ * within the sentry.  It allows for the easy creation of separate <strong>Services</strong> and
+ * allows for multiple implementations of that service to be defined and easily loaded.  It is able
+ * to get all implementations or a single one depending on configuration and need.
+ * </p>
+ * <p>
+ *   The Sentry API module takes advantage of the
+ * <a href="https://docs.oracle.com/javase/tutorial/ext/basics/spi.html">Java Service Loader</a>
+ * which was introduced in Java 7.  It uses the Factory pattern in order to get concrete instances
+ * of Services and allow for custom initialization of those concrete instances.
+ * </p>
+ *
+ * <h2>Create an Service Instance:</h2>
+ *    <dl>
+ *      <dt>Create concert classes for the Services Provider and ProviderFactory interfaces.</dt>
+ *      <dd>
+ *        <p>In order to create a service instance you need to create instances of the services
+ *        Provider and ProviderFactory interfaces.  These should contain all of the necessary
+ *        functions defined by the interface.</p>
+ *
+ *        <p>The ProviderFactory instance needs to have the getId() functioned defined to return a unique
+ *        name for the Service Provider instance so that it can be looked up by that name.</p>
+ *
+ *        <p>The create() function of the ProviderFactory will return a concert instance of the Provider</p>
+ *   <strong>Sample src/main/resources/META-INF/services/org.apache.sentry.spi.FooSomeProvider file</strong>
+ *     <pre>{@code
+ *   package org.apache.sentry.fake;
+ *
+ *   public class FooSomeProvider implements SomeProvider {
+ *     public void doSomething(){
+ *       ... does something ...
+ *     }
+ *   }
+ *   }</pre>
+ *
+ *   <strong>Sample src/main/resources/META-INF/services/org.apache.sentry.spi.FooSomeProviderFactory file</strong>
+ *     <pre>{@code
+ *   package org.apache.sentry.fake;
+ *
+ *   public class FooSomeProviderFactory implements SomeProviderFactory {
+ *     @Override
+ *     public String getId() {
+ *        return "foo"
+ *     }
+ *
+ *     @Override
+ *     public SomeProvider create() {
+ *       return new SomeProvider();
+ *     }
+ *   }
+ *   }</pre>
+ *      </dd>
+ *      <dt>Create an entry for the ProviderFactory instance in the service configuration file for
+ *      the SPI</dt>
+ *      <dd>
+ *        <p>The service configuration file is placed in the META-INF/services directory of a jar and
+ *        is named after the ProviderFactory instance of the SPI.</p>
+ *       <strong>Sample src/main/resources/META-INF/services/org.apache.sentry.fake.SomeProviderFactory file</strong>
+ *       <pre>{@code
+ *       org.apache.sentry.fake.FooSomeProviderFactory
+ *       }</pre>
+ *      </dd>
+ *      <dt>Load the Service instance with the ProviderManager</dt>
+ *      <dd>
+ *        <p>You can then load the service from code with the ProviderManager.  Either all service
+ *        providers or an individual one.</p>
+ *       <pre>{@code
+ *       # Loads instances of all SomeProviderFactory defined
+ *       List<SomeProviderFactory> = ProviderManager.getInstance().load("some-spi");
+ *
+ *       # Loads only the "foo" instance of the SomeProviderFactory
+ *       SomeProviderFactory someProviderFactory = ProviderManager.getInstance().load("some-spi", "foo");
+ *       }</pre>
+ *      </dd>
+ *
+ *
+ *
+ * <h2>How to Implement a New Service:</h2>
+ * <dl>
+ *   <dt>Create an SPI implantation</dt>
+ *   <dd>
+ *     <p>
+ *       You can create Service by implementing a concrete instance of the SPI interface.
+ *       This interface will provider information about what interfaces the SPI will be looking for
+ *       when loading instances.  It requires the Provider and the ProviderFactory information as
+ *       well as a unique name for the Service in the system.
+ *   </p>
+ *   <strong>Sample src/main/java/org/apache/sentry/fake/SomeSpi.java file</strong>
+ *   <pre>{@code
+ *   package org.apache.sentry.fake;
+ *
+ *   public class SomeSpi implements Spi {
+ *
+ *   @Override
+ *   public String getName() {
+ *     return "some-spi";
+ *   }
+ *
+ *   @Override
+ *   public Class<? extends Provider> getProviderClass() {
+ *     return SomeProvider.class;
+ *   }
+ *
+ *   @Override
+ *   public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ *     return SomeProviderFactory.class;
+ *   }
+ * }
+ *   }</pre>
+ *   <p>
+ *      As well you must put an entry for the SPI concrete class in the
+ *      <strong>META-INF/services/org.apache.sentry.spi.Spi</strong> service configuration file pointing to that instance.
+ *   </p>
+ *   <strong>Sample src/main/resources/META-INF/services/org.apache.sentry.spi.SomeSpi file</strong>
+ *   <pre>{@code
+ *   org.apache.sentry.fake.SomeSpi
+ *   org.apache.sentry.fake.SomeOtherSpi
+ *   }</pre>
+ *   <dt>Create a the Provider and Provider Factory interfaces</dt>
+ *   <dd>
+ *     <p>You need to create the interfaces referenced in the the SPI class.  These extend the
+ *     Provider and ProviderFactory interfaces and can be customized to have the function definitions
+ *     for how you want your service to operate.</p>
+ *
+ *   <strong>Sample rc/main/java/org/apache/sentry/fake/SomeProvider.java file</strong>
+ *     <pre>{@code
+ *   package org.apache.sentry.fake;
+ *
+ *   public interface SomeProvider extends Provider {
+ *     void doSomething();
+ *   }
+ *
+ *   }</pre>
+ *   <strong>Sample src/main/java/org/apache/sentry/fake/SomeProviderFactory.java file</strong>
+ *   <pre>{@code
+ *   package org.apache.sentry.fake;
+ *
+ *   public interface SomeProviderFactory extends ProviderFactory<SomeProvider> {
+ *      void init(SomeConfig  config);
+ *   }
+ *   }</pre>
+ *
+ *   </dd>
+ * </dl>
+ *
+ *
+ *
+ */
+package org.apache.sentry.spi;
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProvider.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProvider.java
new file mode 100644
index 0000000..f42e60f
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * Simple Test Provider interface
+ */
+public interface SomeTestProvider extends Provider {
+
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderFactory.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderFactory.java
new file mode 100644
index 0000000..a9b6967
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * Simple Test Provider Factory Interface
+ */
+public interface SomeTestProviderFactory extends ProviderFactory {
+
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplA.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplA.java
new file mode 100644
index 0000000..b5599e1
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplA.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * Implementation of SomeTestProvider
+ */
+public class SomeTestProviderImplA implements SomeTestProviderFactory, SomeTestProvider {
+
+  @Override
+  public Provider create() {
+    return this;
+  }
+
+  @Override
+  public String getId() {
+    return "A";
+  }
+
+  @Override
+  public void close() {
+    
+  }
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplB.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplB.java
new file mode 100644
index 0000000..6291b75
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplB.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * Implementation of SomeTestProvider
+ */
+public class SomeTestProviderImplB implements SomeTestProviderFactory, SomeTestProvider {
+
+  @Override
+  public Provider create() {
+    return this;
+  }
+
+  @Override
+  public String getId() {
+    return "B";
+  }
+
+  @Override
+  public void close() {
+    
+  }
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplNotLoaded.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplNotLoaded.java
new file mode 100644
index 0000000..448a59d
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestProviderImplNotLoaded.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ * Unused Implementation of SomeTestProvider
+ */
+public class SomeTestProviderImplNotLoaded implements SomeTestProviderFactory, SomeTestProvider {
+
+  @Override
+  public Provider create() {
+    return this;
+  }
+
+  @Override
+  public String getId() {
+    return "NotLoaded";
+  }
+
+  @Override
+  public void close() {
+    
+  }
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestSpi.java b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestSpi.java
new file mode 100644
index 0000000..9d3a7da
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/SomeTestSpi.java
@@ -0,0 +1,43 @@
+/*
+ * 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.sentry.spi;
+
+/**
+ *  Implementation of SomeTest as an SPI
+ */
+public class SomeTestSpi implements Spi {
+
+  public static final String SPI = "test";
+
+  @Override
+  public String getName() {
+    return SPI;
+  }
+
+  @Override
+  public Class<? extends Provider> getProviderClass() {
+    return SomeTestProvider.class;
+  }
+
+  @Override
+  public Class<? extends ProviderFactory> getProviderFactoryClass() {
+    return SomeTestProviderFactory.class;
+  }
+}
diff --git a/sentry-spi/src/test/java/org/apache/sentry/spi/TestProviderManager.java b/sentry-spi/src/test/java/org/apache/sentry/spi/TestProviderManager.java
new file mode 100644
index 0000000..b476448
--- /dev/null
+++ b/sentry-spi/src/test/java/org/apache/sentry/spi/TestProviderManager.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.sentry.spi;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testing of the SPI and Provider mechanisms
+ */
+public class TestProviderManager {
+
+  @Test
+  public void testLoadingAll() {
+    List<SomeTestProviderFactory> factories = ProviderManager.getInstance().load(SomeTestSpi.SPI);
+    Assert.assertEquals("Wrong count of service providers found", 2, factories.size());
+    Assert.assertEquals("Missing instance of A", 1,
+        factories.stream().filter(spi -> spi instanceof SomeTestProviderImplA).count());
+    Assert.assertEquals("Missing instance of B", 1,
+        factories.stream().filter(spi -> spi instanceof SomeTestProviderImplB).count());
+    Assert.assertEquals("Instance loaded that should not be.", 0,
+        factories.stream().filter(spi -> spi instanceof SomeTestProviderImplNotLoaded).count());
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testInvalidSPI() {
+    ProviderManager.getInstance().load("NotThere");
+  }
+
+  @Test
+  public void testLoadingSingle() {
+    SomeTestProviderFactory factory = ProviderManager.getInstance().load(SomeTestSpi.SPI, "A");
+    Assert.assertNotNull("Service provider should have been found", factory);
+    Assert.assertEquals("Wrong service provider loaded", "A", factory.getId());
+  }
+
+  @Test
+  public void testLoadingMissingSingle() {
+    SomeTestProviderFactory factory = ProviderManager.getInstance().load(SomeTestSpi.SPI, "Nope");
+    Assert.assertNull("Service provider should not have been found", factory);
+  }
+}
diff --git a/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.SomeTestProviderFactory b/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.SomeTestProviderFactory
new file mode 100644
index 0000000..bb7db3d
--- /dev/null
+++ b/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.SomeTestProviderFactory
@@ -0,0 +1,20 @@
+#
+# 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.sentry.spi.SomeTestProviderImplA
+org.apache.sentry.spi.SomeTestProviderImplB
\ No newline at end of file
diff --git a/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.Spi b/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.Spi
new file mode 100644
index 0000000..904f761
--- /dev/null
+++ b/sentry-spi/src/test/resources/META-INF/services/org.apache.sentry.spi.Spi
@@ -0,0 +1,20 @@
+#
+# 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.sentry.spi.SomeTestSpi