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