Search for polymorphic types using ServiceLoader

This allows to have dynamic implementations of types, that cannot be
defined as subtypes at compile time, but will be discovered at runtime.
This commit is contained in:
Nemanja Zbiljić 2017-10-15 14:45:54 +02:00
parent c39ae8c7ca
commit 252fda11a7
7 changed files with 169 additions and 0 deletions

View File

@ -382,6 +382,21 @@ public class ConfigBeanImpl {
}
}
Map<String, Class<?>> subtypes2 = discoverServices(clazz);
for (Map.Entry<String, Class<?>> entry : subtypes2.entrySet()) {
// handle abstract
if (Modifier.isAbstract(entry.getValue().getModifiers())) {
result.putAll(findAllSubtypes(entry.getValue()));
continue;
}
// add concrete type
String typeName = findTypeName(entry.getValue());
if (typeName == null) {
typeName = entry.getKey();
}
result.putIfAbsent(typeName, entry.getValue());
}
return result;
}
@ -406,4 +421,45 @@ public class ConfigBeanImpl {
return (tn == null) ? null : tn.value();
}
private static Map<String, Class<?>> discoverServices(Class<?> clazz) {
// using TCCL
java.util.ServiceLoader<?> services = java.util.ServiceLoader.load(clazz);
if (services.iterator().hasNext()) {
return extractDiscoveredServices(services.iterator());
} else {
// By default ServiceLoader.load uses the TCCL, this may not be
// enough in environment dealing with classloaders differently
// such as OSGi. So we should try to use the classloader having
// loaded this class.
services = java.util.ServiceLoader.load(clazz, ConfigBeanImpl.class.getClassLoader());
if (services.iterator().hasNext()) {
return extractDiscoveredServices(services.iterator());
} else {
return java.util.Collections.emptyMap();
}
}
}
private static Map<String, Class<?>> extractDiscoveredServices(java.util.Iterator<?> servicesIterator) {
Map<String, Class<?>> serviceClasses = new HashMap<String, Class<?>>(4);
// use while(true) loop so that we can isolate all service loader
// errors from .next and .hasNext to a single service
while (true) {
try {
if (!servicesIterator.hasNext()) {
break;
}
Class<?> serviceClass = servicesIterator.next().getClass();
String typeName = findTypeName(serviceClass);
if (typeName == null) {
typeName = serviceClass.getSimpleName();
}
serviceClasses.put(typeName, serviceClass);
} catch (java.util.ServiceConfigurationError error) {
throw new IllegalArgumentException("Found error loading a service", error);
}
}
return serviceClasses;
}
}

View File

@ -0,0 +1,43 @@
package beanconfig.polymorphic;
import com.typesafe.config.ConfigTypeInfo;
import com.typesafe.config.ConfigTypeName;
public class WithServiceLoader {
public interface ExampleTag {
}
@ConfigTypeInfo
public interface ExampleSPI extends ExampleTag {
}
@ConfigTypeName("a")
public static class ImplA implements ExampleSPI {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@ConfigTypeName("b")
public static class ImplB implements ExampleSPI {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,31 @@
package beanconfig.polymorphic;
import beanconfig.polymorphic.WithServiceLoader.ExampleSPI;
import java.util.List;
public class WithServiceLoaderConfigs {
public static class SimpleServiceLoader {
private ExampleSPI singleTag;
private List<ExampleSPI> otherTags;
public ExampleSPI getSingleTag() {
return singleTag;
}
public void setSingleTag(ExampleSPI singleTag) {
this.singleTag = singleTag;
}
public List<ExampleSPI> getOtherTags() {
return otherTags;
}
public void setOtherTags(List<ExampleSPI> otherTags) {
this.otherTags = otherTags;
}
}
}

View File

@ -0,0 +1,2 @@
beanconfig.polymorphic.WithServiceLoader$ImplA
beanconfig.polymorphic.WithServiceLoader$ImplB

View File

@ -0,0 +1 @@
beanconfig.polymorphic.WithServiceLoader$ExampleSPI

View File

@ -105,4 +105,21 @@
}
}
}
"withServiceLoader": {
"singleTag": {
"type": "a"
"message": "bar"
}
"otherTags": [
{
"type": "a"
"message": "baz"
}
{
"type": "b"
"value": 42
}
]
}
}

View File

@ -11,6 +11,8 @@ import beanconfig.polymorphic.TypeNamesConfigs._
import beanconfig.polymorphic.VisibleTypeId._
import beanconfig.polymorphic.VisibleTypeIdConfigs._
import beanconfig.polymorphic.WithGenericsConfigs._
import beanconfig.polymorphic.WithServiceLoader._
import beanconfig.polymorphic.WithServiceLoaderConfigs._
import beanconfig.polymorphic.{TypeNames, WithGenerics}
import com.typesafe.config._
import org.junit.Assert._
@ -175,6 +177,23 @@ class ConfigPolymorphicBeanTest extends TestUtils {
assertEquals(3, beanConfig.getWrapperWithDog.getAnimal.asInstanceOf[WithGenerics.Dog].getBoneCount)
}
@Test
def testSimpleServiceLoader() {
val beanConfig: SimpleServiceLoader =
ConfigBeanFactory.create(loadConfig().getConfig("withServiceLoader"), classOf[SimpleServiceLoader])
assertNotNull(beanConfig)
assertTrue(beanConfig.getSingleTag.isInstanceOf[ImplA])
assertEquals("bar", beanConfig.getSingleTag.asInstanceOf[ImplA].getMessage)
assertEquals(2, beanConfig.getOtherTags.size)
assertTrue(beanConfig.getOtherTags.get(0).isInstanceOf[ImplA])
assertEquals("baz", beanConfig.getOtherTags.get(0).asInstanceOf[ImplA].getMessage)
assertTrue(beanConfig.getOtherTags.get(1).isInstanceOf[ImplB])
assertEquals(42, beanConfig.getOtherTags.get(1).asInstanceOf[ImplB].getValue)
}
private def loadConfig(): Config = {
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/polymorphic01.conf")
try {