From 21e60d69fbda42bd6229e2120594773e12c04914 Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 9 May 2022 19:54:49 +0200 Subject: [PATCH 1/6] Added SpiServiceLocators to support multiple SpiServiceLocator annotations on package-info.java --- README.md | 11 +++ .../spiap/api/SpiServiceLocators.java | 19 +++++ .../spiap/processor/SpiProcessor.java | 70 +++++++++++++------ .../spiap/processor/package-info.java | 5 +- 4 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 api/src/main/java/io/toolisticon/spiap/api/SpiServiceLocators.java diff --git a/README.md b/README.md index 333904b..8bc9bee 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,17 @@ Then add the SpiServiceLocator annotation in it: The locator will be created in the annotated package. It is named like the SPI suffixed with ServiceLocator (ExampleSpiInterfaceServiceLocator in this example) +To create multiple service locators in the same package use @SpiServiceLocators: + + + @SpiServiceLocators({ + @SpiServiceLocator(FirstExampleSpiInterface.class), + @SpiServiceLocator(SecondExampleSpiInterface.class), + }) + package your.target.package; + + import io.toolisticon.spiap.api.SpiServiceLocator; + import your.spi.package.ExampleSpiInterface; ## How to register a service implementation Just add a Service annotation to your service implementation: diff --git a/api/src/main/java/io/toolisticon/spiap/api/SpiServiceLocators.java b/api/src/main/java/io/toolisticon/spiap/api/SpiServiceLocators.java new file mode 100644 index 0000000..30eb4d9 --- /dev/null +++ b/api/src/main/java/io/toolisticon/spiap/api/SpiServiceLocators.java @@ -0,0 +1,19 @@ +package io.toolisticon.spiap.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Binder for supporting several {@link SpiServiceLocator} in a single class. + * + * @since 0.8.2 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.PACKAGE}) +public @interface SpiServiceLocators { + + SpiServiceLocator[] value(); + +} diff --git a/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java b/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java index 61fa25a..710ab43 100644 --- a/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java +++ b/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java @@ -7,13 +7,13 @@ import io.toolisticon.aptk.tools.generators.SimpleJavaWriter; import io.toolisticon.spiap.api.Spi; import io.toolisticon.spiap.api.SpiServiceLocator; +import io.toolisticon.spiap.api.SpiServiceLocators; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -24,7 +24,8 @@ */ public class SpiProcessor extends AbstractAnnotationProcessor { - private final static Set SUPPORTED_ANNOTATIONS = createSupportedAnnotationSet(Spi.class, SpiServiceLocator.class); + private final static Set SUPPORTED_ANNOTATIONS = + createSupportedAnnotationSet(Spi.class, SpiServiceLocator.class, SpiServiceLocators.class); @Override @@ -36,7 +37,7 @@ public Set getSupportedAnnotationTypes() { public boolean processAnnotations(Set annotations, RoundEnvironment roundEnv) { handleSpiAnnotation(roundEnv); - handeSpiServiceLocatorAnnotation(roundEnv); + handleSpiServiceLocatorAnnotation(roundEnv); return false; } @@ -57,11 +58,11 @@ private void handleSpiAnnotation(RoundEnvironment roundEnv) { // Generate service locator depending on settings configured in Spi annotation SpiWrapper spiAnnotation = SpiWrapper.wrapAnnotationOfElement(element); - if (spiAnnotation.generateServiceLocator()) { + if (spiAnnotation != null && spiAnnotation.generateServiceLocator()) { // Now create the service locator TypeElement typeElement = ElementUtils.CastElement.castInterface(element); - PackageElement packageElement = (PackageElement) ElementUtils.AccessEnclosingElements.getFirstEnclosingElementOfKind(typeElement, ElementKind.PACKAGE); + PackageElement packageElement = ElementUtils.AccessEnclosingElements.getFirstEnclosingElementOfKind(typeElement, ElementKind.PACKAGE); // generate service locator generateServiceLocator(element, packageElement, typeElement); @@ -71,42 +72,65 @@ private void handleSpiAnnotation(RoundEnvironment roundEnv) { } - private void handeSpiServiceLocatorAnnotation(RoundEnvironment roundEnv) { + private void handleSpiServiceLocatorAnnotation(RoundEnvironment roundEnv) { - // handle Spi annotation + // handle SpiServiceLocator annotation for (Element element : roundEnv.getElementsAnnotatedWith(SpiServiceLocator.class)) { MessagerUtils.info(element, "Process : " + element.getSimpleName() + " annotated with SpiServiceLocator annotation"); // get type from annotation - SpiServiceLocatorWrapper annotationWrapper = SpiServiceLocatorWrapper.wrapAnnotationOfElement(element); - TypeMirror typeMirror = annotationWrapper.valueAsTypeMirror(); - if (typeMirror == null) { - MessagerUtils.error(element, "Couldn't get type from annotations attributes"); - continue; - } + SpiServiceLocatorWrapper spiWrapper = SpiServiceLocatorWrapper.wrapAnnotationOfElement(element); + handleSpiServiceLocationWrapper(spiWrapper, element); - TypeElement serviceLocatorInterfaceElement = annotationWrapper.valueAsTypeMirrorWrapper().getTypeElement(); + } - // check if it is place on interface - if (!ElementUtils.CheckKindOfElement.isInterface(serviceLocatorInterfaceElement)) { - MessagerUtils.error(element, SpiProcessorMessages.ERROR_SERVICE_LOCATOR_PASSED_SPI_CLASS_MUST_BE_AN_INTERFACE); + // handle SpiServiceLocator annotation + for (Element element : roundEnv.getElementsAnnotatedWith(SpiServiceLocators.class)) { + + MessagerUtils.info(element, "Process : " + element.getSimpleName() + " annotated with SpiServiceLocator annotation"); + + // get type from annotation + SpiServiceLocatorsWrapper annotationWrapper = SpiServiceLocatorsWrapper.wrapAnnotationOfElement(element); + if (annotationWrapper == null) { continue; } + for (SpiServiceLocatorWrapper spiWrapper : annotationWrapper.value()) { + handleSpiServiceLocationWrapper(spiWrapper, element); + } + + } + + } + + private void handleSpiServiceLocationWrapper( + final SpiServiceLocatorWrapper annotationWrapper, final Element element) { - // Now create the service locator - TypeElement typeElement = ElementUtils.CastElement.castInterface(serviceLocatorInterfaceElement); - PackageElement packageElement = (PackageElement) element; + if (annotationWrapper == null || annotationWrapper.valueAsTypeMirror() == null) { + MessagerUtils.error(element, "Couldn't get type from annotations attributes"); + return; + } + System.out.println("Handling : " + annotationWrapper.valueAsFqn()); - // generate service locator - generateServiceLocator(element, packageElement, typeElement); + TypeElement serviceLocatorInterfaceElement = annotationWrapper.valueAsTypeMirrorWrapper().getTypeElement(); + // check if it is place on interface + if (!ElementUtils.CheckKindOfElement.isInterface(serviceLocatorInterfaceElement)) { + MessagerUtils.error(element, SpiProcessorMessages.ERROR_SERVICE_LOCATOR_PASSED_SPI_CLASS_MUST_BE_AN_INTERFACE); + return; } - } + // Now create the service locator + TypeElement typeElement = ElementUtils.CastElement.castInterface(serviceLocatorInterfaceElement); + PackageElement packageElement = (PackageElement) element; + + + // generate service locator + generateServiceLocator(element, packageElement, typeElement); + } private void generateServiceLocator(Element annotatedElement, PackageElement packageElement, TypeElement typeElement) { diff --git a/processor/src/main/java/io/toolisticon/spiap/processor/package-info.java b/processor/src/main/java/io/toolisticon/spiap/processor/package-info.java index b3e236e..1bed15f 100644 --- a/processor/src/main/java/io/toolisticon/spiap/processor/package-info.java +++ b/processor/src/main/java/io/toolisticon/spiap/processor/package-info.java @@ -1,11 +1,12 @@ /** * This package contains the annotation processors. */ -@AnnotationWrapper({Service.class, Services.class, SpiServiceLocator.class, Spi.class}) +@AnnotationWrapper({Service.class, Services.class, SpiServiceLocator.class, SpiServiceLocators.class, Spi.class}) package io.toolisticon.spiap.processor; import io.toolisticon.aptk.annotationwrapper.api.AnnotationWrapper; import io.toolisticon.spiap.api.Service; import io.toolisticon.spiap.api.Services; import io.toolisticon.spiap.api.Spi; -import io.toolisticon.spiap.api.SpiServiceLocator; \ No newline at end of file +import io.toolisticon.spiap.api.SpiServiceLocator; +import io.toolisticon.spiap.api.SpiServiceLocators; From f6c9a65286406778a474d8850be7256e6ad33ab1 Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 9 May 2022 20:40:11 +0200 Subject: [PATCH 2/6] Added SpiServiceLocators to support multiple SpiServiceLocator annotations on package-info.java --- .../main/java/io/toolisticon/spiap/processor/SpiProcessor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java b/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java index 710ab43..2136fae 100644 --- a/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java +++ b/processor/src/main/java/io/toolisticon/spiap/processor/SpiProcessor.java @@ -112,8 +112,6 @@ private void handleSpiServiceLocationWrapper( return; } - System.out.println("Handling : " + annotationWrapper.valueAsFqn()); - TypeElement serviceLocatorInterfaceElement = annotationWrapper.valueAsTypeMirrorWrapper().getTypeElement(); // check if it is place on interface From 1228d97c94ce43450bdefd6933a5922274cef21c Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 9 May 2022 21:13:54 +0200 Subject: [PATCH 3/6] Test for multiple SpiServiceLocators --- .../SpiServiceLocatorsProcessorTest.java | 38 +++++++++++++++++++ .../multipleSpiLocators/package-info.java | 13 +++++++ 2 files changed, 51 insertions(+) create mode 100644 processor/src/test/java/io/toolisticon/spiap/processor/SpiServiceLocatorsProcessorTest.java create mode 100644 processor/src/test/resources/spiprocessor/spiservicelocator/multipleSpiLocators/package-info.java diff --git a/processor/src/test/java/io/toolisticon/spiap/processor/SpiServiceLocatorsProcessorTest.java b/processor/src/test/java/io/toolisticon/spiap/processor/SpiServiceLocatorsProcessorTest.java new file mode 100644 index 0000000..325190c --- /dev/null +++ b/processor/src/test/java/io/toolisticon/spiap/processor/SpiServiceLocatorsProcessorTest.java @@ -0,0 +1,38 @@ +package io.toolisticon.spiap.processor; + +import io.toolisticon.aptk.tools.MessagerUtils; +import io.toolisticon.cute.CompileTestBuilder; +import io.toolisticon.cute.JavaFileObjectUtils; +import javax.tools.StandardLocation; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests of {@link SpiProcessor}. + */ + +public class SpiServiceLocatorsProcessorTest { + + + @Before + public void init() { + MessagerUtils.setPrintMessageCodes(true); + } + + private CompileTestBuilder.CompilationTestBuilder compileTestBuilder = CompileTestBuilder + .compilationTest() + .addProcessors(SpiProcessor.class); + + @Test + public void test_validUsage() { + + compileTestBuilder + .addSources("spiprocessor/spiservicelocator/multipleSpiLocators/package-info.java") + .compilationShouldSucceed() + .expectThatGeneratedSourceFileExists("io.toolisticon.spiap.processor.TestSpiServiceLocator") + .expectThatGeneratedSourceFileExists("io.toolisticon.spiap.processor.AnotherTestSpiServiceLocator") + .executeTest(); + + } + +} diff --git a/processor/src/test/resources/spiprocessor/spiservicelocator/multipleSpiLocators/package-info.java b/processor/src/test/resources/spiprocessor/spiservicelocator/multipleSpiLocators/package-info.java new file mode 100644 index 0000000..34aa316 --- /dev/null +++ b/processor/src/test/resources/spiprocessor/spiservicelocator/multipleSpiLocators/package-info.java @@ -0,0 +1,13 @@ +/** + * This package contains the annotation processors. + */ +@SpiServiceLocators({ + @SpiServiceLocator(TestSpi.class), + @SpiServiceLocator(AnotherTestSpi.class) +}) +package io.toolisticon.spiap.processor; + +import io.toolisticon.spiap.api.SpiServiceLocator; +import io.toolisticon.spiap.api.SpiServiceLocators; +import io.toolisticon.spiap.processor.serviceprocessortest.AnotherTestSpi; +import io.toolisticon.spiap.processor.serviceprocessortest.TestSpi; From 811b30e7d4eb8bb81fef7f480474526ddba996bc Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 16 May 2022 15:47:22 +0200 Subject: [PATCH 4/6] ServiceLocator templated refactored for JDK8 --- .../src/main/resources/ServiceLocator.tpl | 624 +++++++++++------- 1 file changed, 379 insertions(+), 245 deletions(-) diff --git a/processor/src/main/resources/ServiceLocator.tpl b/processor/src/main/resources/ServiceLocator.tpl index e8c4e03..ab65688 100644 --- a/processor/src/main/resources/ServiceLocator.tpl +++ b/processor/src/main/resources/ServiceLocator.tpl @@ -1,333 +1,467 @@ package ${ package }; import ${ canonicalName }; + +import io.toolisticon.spiap.api.OutOfService; import io.toolisticon.spiap.api.Service; import io.toolisticon.spiap.api.Services; -import io.toolisticon.spiap.api.OutOfService; +import java.io.IOException; import java.io.InputStream; -import java.lang.ClassLoader; -import java.lang.NoClassDefFoundError; -import java.lang.NumberFormatException; -import java.lang.Thread; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.Set; -import java.util.HashSet; +import java.util.Optional; import java.util.Properties; import java.util.ServiceLoader; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** - * A generic service locator. + * Service locator for ${ simpleName } to provide SPI implementations. */ -public class ${ simpleName }ServiceLocator { - - private static ClassLoader classLoaderToUse = null; +public enum ${ simpleName }ServiceLocator { - /** - * Caches all loaded services. - */ - private static List serviceImplementations; + INSTANCE; - /** - * Exception that is thrown if a specific service implementation can't be found. - */ - public static class ImplementationNotFoundException extends RuntimeException{ - - public ImplementationNotFoundException(String id) { - super(String.format("Couldn't find implementation for spi ${simpleName} with id : '%s'",id)); - } + /** + * Caches all loaded services until reload is invoked or classloader has been changed. + */ + private final List serviceImplementations = new ArrayList<>(); - } + /** Classloader used for service loading. */ + private ClassLoader classLoaderToUse = null; - /** - * Key class for looking checking available spi service implementations. - */ - public static class ServiceImplementation { - - private final String id; - private final String description; - private final int priority; - private final boolean outOfService; - private final ${ simpleName } serviceInstance; - - /** - * Initial default config - */ - private ServiceImplementation(${ simpleName } serviceInstance) { - this.serviceInstance = serviceInstance; - this.id = serviceInstance.getClass().getCanonicalName(); - this.description = ""; - this.priority = 0; - this.outOfService = false; - } - private ServiceImplementation(ServiceImplementation previousConfig, String id, String description, Integer priority, Boolean outOfService) { - this.serviceInstance = previousConfig.getServiceInstance(); - this.id = id != null ? id : previousConfig.getId(); - this.description = description != null ? description : previousConfig.getDescription(); - this.priority = priority !=null ? priority : previousConfig.getPriority(); - this.outOfService = outOfService != null ? outOfService : previousConfig.isOutOfService(); - } + /** + * Get {@link ServiceImplementation} for {@link ${ simpleName }} implementation. + * + * @param serviceImpl + * of type ${ simpleName } + * @return ServiceImplementation + */ + public static ServiceImplementation getServiceImplementation( + final ${ simpleName } serviceImpl) { + // create default config + ServiceImplementation serviceKey = new ServiceImplementation(serviceImpl); - public String getId() { - return id; - } + // try to get ServiceKey from annotation + serviceKey = INSTANCE.getServiceImplementationByAnnotations(serviceKey, serviceImpl); + serviceKey = INSTANCE.getServiceImplementationByProperty(serviceKey, serviceImpl); + return serviceKey; - public String getDescription() { - return description; - } + } - public int getPriority() { - return priority; - } - public boolean isOutOfService () { - return outOfService; - } + /** + * Get {@link ServiceImplementation} for all available implementations. + * + * @return a list that contains ServiceImplementations for all available service + * implementations, or an empty list if none could be found. + */ + public static List getServiceImplementations() { - public ${ simpleName } getServiceInstance () { - return serviceInstance; - } + return new ArrayList<>(INSTANCE.loadImplementations()); + } - } + /** + * Locate a service implementation with a specific id. + * + * @param id + * the id to search + * @return the service implementation with the id + * @throws ImplementationNotFoundException + * if either passed id is null or if the service implementation can't be found. + */ + public static ${ simpleName } locateById(final String id) { + return locateImplById(id).orElseThrow(() -> new ImplementationNotFoundException(id)); + } - private static ServiceImplementation getServiceImplementationByAnnotations(ServiceImplementation previousConfig, ${ simpleName } serviceImpl) { - - try { - Service serviceAnnotation = serviceImpl.getClass().getAnnotation(Service.class); - if (serviceAnnotation == null) { - Services servicesAnnotation = serviceImpl.getClass().getAnnotation(Services.class); - if (servicesAnnotation != null) { - for (Service service : servicesAnnotation.value()) { - if (${ simpleName }.class.equals(service.value())) { - serviceAnnotation = service; - break; - } - } - } - } - String id = serviceAnnotation != null ? serviceAnnotation.id() : null; - String description = serviceAnnotation != null ? serviceAnnotation.description() : null; - Integer priority = serviceAnnotation != null ? serviceAnnotation.priority() : null; - boolean outOfService = serviceImpl.getClass().getAnnotation(OutOfService.class) != null; - - return new ServiceImplementation(previousConfig, id, description, priority, outOfService); - - } catch (NoClassDefFoundError e) { - // ignore - } - return previousConfig; + /** + * Locate a service implementation with a specific id. + * + * @param id + * the id to search for. + * @return {@code Optional<${ simpleName }>} service implementation identified by the + * given id. + */ + public static Optional<${ simpleName }> locateImplById(final String id) { + if (id == null) { + return Optional.empty(); } - private static ServiceImplementation getServiceImplementationByProperty(ServiceImplementation previousConfig, ${ simpleName } serviceImpl) { - - // first try to get config from property file - final String propertyFileName = "/META-INF/spiap/" + ${ simpleName }.class.getCanonicalName() + "/" + serviceImpl.getClass().getCanonicalName() + ".properties"; - InputStream inputStream = ${ simpleName }ServiceLocator.class.getResourceAsStream(propertyFileName); - if (inputStream != null) { - - try { - Properties properties = new Properties(); - properties.load(inputStream); - - String id = properties.getProperty("${constants.id}"); - String description = properties.getProperty("${constants.description}"); - Integer priority = null; - try { - String priorityString = properties.getProperty("${constants.priority}"); - if (priorityString != null) { - priority = Integer.valueOf(priorityString); - } - } catch (NumberFormatException e) { - // ignore - } - - Boolean outOfService = null; - String outOfServiceString = properties.getProperty("${constants.outOfService}"); - if (outOfServiceString != null) { - outOfService = Boolean.valueOf(outOfServiceString); - } - - return new ServiceImplementation(previousConfig, id, description, priority, outOfService); - - } catch (Exception e) { - // do nothing - } - } - - return previousConfig; - + return getServiceImplementations().stream() + .filter(si -> id.equals(si.getId())) + .findFirst() + .map(ServiceImplementation::getServiceInstance); + } + + + /** + * Locate a service implementation. + * + * If more than one implementation is available, the first one is returned. + * + * Important: Successive calls may return different service implementations. + * + * @return First implementation found via locateAll method call. + */ + public static ${ simpleName } locate() { + + final List<${ simpleName }> services = locateAll(); + return services.isEmpty() + ? null + : services.get(0); + } + + + /** + * Locate all available service implementations. + * + * @return List containing all available service implementations or an empty list, if no + * implementation can be found. + */ + public static List<${ simpleName }> locateAll() { + + return getServiceImplementations().stream() + .map(ServiceImplementation::getServiceInstance) + .collect(Collectors.toList()); + } + + + /** + * Clear cache and reload services. + */ + public static void reloadServices() { + + INSTANCE.serviceImplementations.clear(); + INSTANCE.loadImplementations(); + } + + + /** + * Load service implementations if cache is empty. + * + * @return {@code List} containing the currently available service + * implementations for the classloader. + */ + private List loadImplementations() { + + if (!serviceImplementations.isEmpty()) { + return serviceImplementations; } + final Iterator<${ simpleName }> iterator = + ServiceLoader.load(${ simpleName }.class, getClassLoaderToUse()).iterator(); + + serviceImplementations.addAll( + StreamSupport.stream(Spliterators.spliteratorUnknownSize( + iterator, Spliterator.ORDERED | Spliterator.NONNULL), false) + .map(${ simpleName }ServiceLocator::getServiceImplementation) + .filter(si -> !si.isOutOfService()) + .sorted() + .collect(Collectors.toList())); + return serviceImplementations; + } + + + /** + * Get the service implementation by annotation. + * + * @param previousConfig + * of type ServiceImplementation + * @param serviceImpl + * of type ${ simpleName } + * @return ServiceImplementation + */ + private ServiceImplementation getServiceImplementationByAnnotations( + final ServiceImplementation previousConfig, + final ${ simpleName } serviceImpl) { + + try { + final boolean outOfService = serviceImpl.getClass().getAnnotation(OutOfService.class) != null; + + return getServiceAnnotation(serviceImpl) + .map(s -> new ServiceImplementation( + previousConfig, s.id(), s.description(), s.priority(), outOfService)) + .orElse(new ServiceImplementation( + previousConfig, null, null, null, outOfService)); + } catch (final NoClassDefFoundError ignore) { + // ignore + } - public static ServiceImplementation getServiceImplementation(${ simpleName } serviceImpl) { + return previousConfig; + } - // create default config - ServiceImplementation serviceKey = new ServiceImplementation(serviceImpl); - // try to get ServiceKey from annotation - serviceKey = getServiceImplementationByAnnotations(serviceKey, serviceImpl); + /** + * Find the service by annotation. + * + * @param serviceImpl + * of type ${ simpleName } + * @return {@code Optional} + */ + private Optional getServiceAnnotation( + final ${ simpleName } serviceImpl) { - serviceKey = getServiceImplementationByProperty(serviceKey, serviceImpl); + final Service serviceAnnotation = serviceImpl.getClass().getAnnotation(Service.class); + if (serviceAnnotation != null) { + return Optional.of(serviceAnnotation); + } - return serviceKey; + final Services servicesAnnotation = serviceImpl.getClass().getAnnotation(Services.class); + if (servicesAnnotation != null) { + return Arrays.stream(servicesAnnotation.value()) + .filter(service -> ${ simpleName }.class.equals(service.value())) + .findFirst(); + } + return Optional.empty(); + } + + + /** + * Read the spiap properties file and get ServiceImplementation based on the properties. + * + * If not found, return previousConfig implementation. + * + * @param previousConfig + * of type ServiceImplementation + * @param serviceImpl + * of type ${ simpleName } + * @return ServiceImplementation + */ + private ServiceImplementation getServiceImplementationByProperty( + final ServiceImplementation previousConfig, + final ${ simpleName } serviceImpl) { + + // first try to get config from property file + final String propertyFileName = String.format("/META-INF/spiap/%s/%s.properties", + ${ simpleName }.class.getCanonicalName(), + serviceImpl.getClass().getCanonicalName()); + + return loadServiceProperties(propertyFileName) + .flatMap(p -> getServiceImplementationFromProperties(previousConfig, p)) + .orElse(previousConfig); + } + + + /** + * Build ServiceImplementation based on the given properties. If an error occurs return + * Optional.empty. + * + * @param previousConfig + * of type ServiceImplementation + * @param properties + * of type Properties + * @return {@code Optional} + */ + private Optional getServiceImplementationFromProperties( + final ServiceImplementation previousConfig, + final Properties properties) { + + if (properties == null) { + return Optional.empty(); + } + try { + final String id = properties.getProperty("id"); + final String description = properties.getProperty("description"); + Integer priority = null; + try { + final String priorityString = properties.getProperty("priority"); + if (priorityString != null) { + priority = Integer.valueOf(priorityString); + } + } catch (final NumberFormatException e) { + // ignore + } + + Boolean outOfService = null; + final String outOfServiceString = properties.getProperty("outOfService"); + if (outOfServiceString != null) { + outOfService = Boolean.valueOf(outOfServiceString); + } + return Optional.of( + new ServiceImplementation(previousConfig, id, description, priority, outOfService)); + } catch (final Exception ignore) { + // do nothing + } + return Optional.empty(); + } + + + /** + * Load the spiap properties file from resources. In terms, an error occurs return an empty + * Optional. + * + * @param propertyFileName + * of type String + * @return {@code Optional} + */ + private Optional loadServiceProperties(final String propertyFileName) { + + try (final InputStream inputStream = + ${ simpleName }ServiceLocator.class.getResourceAsStream(propertyFileName)) { + if (inputStream == null) { + return Optional.empty(); + } + final Properties properties = new Properties(); + properties.load(inputStream); + return Optional.of(properties); + } catch (final IOException ignore) { + // No properties found. Ignore. } + return Optional.empty(); + } - /** - * Comparator which allows sorting of service implementations by priority. - */ - public static class ServicePriorityComparator implements Comparator { + /** + * Method getClassLoaderToUse returns the classLoaderToUse of this + * ${ simpleName }ServiceLocator object. + * + * + * + * @return the classLoaderToUse (type ClassLoader) of this ${ simpleName }ServiceLocator + * object. + */ + private ClassLoader getClassLoaderToUse() { - public int compare (ServiceImplementation o1,ServiceImplementation o2){ - if (o1 == null && o2 == null) { - return 0; - } else if (o1 != null && o2 == null) { - return -1; - } else if (o1 == null && o2 != null) { - return -1; - } else { + return classLoaderToUse != null + ? classLoaderToUse + : Thread.currentThread().getContextClassLoader(); + } - if (o1.priority == o2.priority) { - return 0; - } else { - return o1.getPriority() < o2.getPriority() ? -1 : 1; - } - } - } + /** + * Set the classloader to use. + * @param classLoader + * The class loader to be used to load provider-configuration files and provider classes, + * or null if the currents thread is to be used + */ + public static void setClassLoaderToUse(final ClassLoader classLoader) { - } + INSTANCE.serviceImplementations.clear(); + INSTANCE.classLoaderToUse = classLoader; + } - /* - * Get {@link ServiceKey} for all available implementations. - * @return a list that contains ServiceKeys for all available service implementations, or an empty List if none could be found. - */ - public static List<${ simpleName }ServiceLocator.ServiceImplementation> getServiceImplementations() { - - locateAll(); - return new ArrayList(serviceImplementations); - } + /** + * Exception that is thrown if a specific service implementation cannot be found. + */ + public static class ImplementationNotFoundException extends RuntimeException { - /** - * locate a service implementation with a specific id. - * - * @param id the id to search - * @return the service implementation with the id - * @throws ImplementationNotFoundException if either passed id is null or if the service implementation can't be found. - */ - public static ${ simpleName } locateById(String id) { + private static final long serialVersionUID = -8610400812558665841L; - if (id != null) { - locateAll(); + public ImplementationNotFoundException(final String id) { - for (ServiceImplementation serviceImpl : serviceImplementations) { + super(String.format( + "Could not find implementation for spi ${ simpleName } with id : '%s'", id)); + } + } - if (id.equals(serviceImpl.getId())) { - return serviceImpl.getServiceInstance(); - } - } - } + /** + * Key class for looking for available spi service implementations. + */ + public static class ServiceImplementation implements Comparable { - throw new ImplementationNotFoundException(id); + private final String id; + private final String description; + private final int priority; + private final boolean outOfService; + private final ${ simpleName } serviceInstance; - } /** - * Hide constructor. + * Initial default config */ - private ${ simpleName }ServiceLocator() { - } + private ServiceImplementation(final ${ simpleName } serviceInstance) { - /** - * Locates a service implementation. - * The first implementation returned by the locateAll method will be returned if more than one implementation is available. - * Successive calls may return different service implementations. - * @return returns the first Implementation found via locateAll method call. - */ - public static ${ simpleName } locate() { - final List services = locateAll(); - return services.isEmpty() ? (${ simpleName })null : (${ simpleName })services.get(0); + this.serviceInstance = serviceInstance; + this.id = serviceInstance.getClass().getCanonicalName(); + this.description = ""; + this.priority = 0; + this.outOfService = false; } - /** - * Locates all available service implementations. - * @return returns a list containing all available service implementations or an empty list, if no implementation can be found. - */ - public static List< ${ simpleName } > locateAll() { - if (serviceImplementations == null) { + private ServiceImplementation( + final ServiceImplementation previousConfig, + final String id, + final String description, + final Integer priority, + final Boolean outOfService) { - final Iterator<${ simpleName }> iterator = ServiceLoader.load(${ simpleName }.class, getClassLoaderToUse()).iterator(); - final List tmpServiceImplementations = new ArrayList(); + this.serviceInstance = previousConfig.getServiceInstance(); + this.id = id != null ? id : previousConfig.getId(); + this.description = description != null ? description : previousConfig.getDescription(); + this.priority = priority != null ? priority : previousConfig.getPriority(); + this.outOfService = outOfService != null ? outOfService : previousConfig.isOutOfService(); + } - while (iterator.hasNext()) { - try { - ${ simpleName } service = iterator.next(); + public String getId() { - ServiceImplementation serviceImpl = getServiceImplementation(service); - if (!serviceImpl.isOutOfService()) { - tmpServiceImplementations.add(serviceImpl); - } + return id; + } - } - catch (Error e) { - e.printStackTrace(System.err); - } - } - Collections.sort(tmpServiceImplementations, new ServicePriorityComparator()); + public String getDescription() { - serviceImplementations = tmpServiceImplementations; + return description; + } - } - // Now return services - final List<${ simpleName }> services = new ArrayList<${ simpleName }>(); + public int getPriority() { - for (ServiceImplementation service : serviceImplementations) { - services.add(service.getServiceInstance()); - } + return priority; + } - return services; + public boolean isOutOfService() { + + return outOfService; } - /** - * Clear cache and reload services. - */ - public static void reloadServices() { - serviceImplementations = null; - locateAll(); - } + public ${ simpleName } getServiceInstance() { - /** - * Sets the classloader to use. - * @param classLoader The class loader to be used to load provider-configuration files and provider classes, or null if the currents thread is to be used - */ - public static void setClassLoaderToUse(ClassLoader classLoader) { - classLoaderToUse = classLoader; + return serviceInstance; } - private static ClassLoader getClassLoaderToUse () { - return classLoaderToUse != null ? classLoaderToUse : Thread.currentThread().getContextClassLoader(); + /** + * Compare by priority. + * + * @param o + * of type ServiceImplementation + * + * @return int + */ + @Override + public int compareTo(final ServiceImplementation o) { + + if (o == null) { + return -1; + } + if (this == o) { + return 0; + } + if (this.getPriority() == o.getPriority()) { + return 0; + } else { + return this.getPriority() < o.getPriority() ? -1 : 1; + } } - + } } From e83fd52d97fedc7385e5458fd7150bf3678a63ce Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 16 May 2022 15:48:50 +0200 Subject: [PATCH 5/6] Added prerequisites to README.md --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8bc9bee..a4c5f2b 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,13 @@ # Why you should use this project? If you want to use a service provider interface (_SPI_) you need to register your service implementation in the _/META-INF/services/<Full qualified spi interface name>_ file. -Additionally you usually need to write a service locator to be able to use the service implementation. +Additionally, you usually need to write a service locator to be able to use the service implementation. The annotation processor offered by this project provides exactly this. It allows you to create the service locator file just by adding an annotation to you spi implementation. -Additionally it will generate a service locator for you. +Additionally, it will generate a service locator for you. + +# Prerequisites +- The generated ServiceLocator code requires at least JDK 8. # Features Annotation processor that @@ -75,12 +78,13 @@ To create multiple service locators in the same package use @SpiServiceLocators: @SpiServiceLocators({ @SpiServiceLocator(FirstExampleSpiInterface.class), - @SpiServiceLocator(SecondExampleSpiInterface.class), + @SpiServiceLocator(SecondExampleSpiInterface.class) }) package your.target.package; import io.toolisticon.spiap.api.SpiServiceLocator; - import your.spi.package.ExampleSpiInterface; + import your.spi.package.FirstExampleSpiInterface; + import your.spi.package.SecondExampleSpiInterface; ## How to register a service implementation Just add a Service annotation to your service implementation: @@ -96,7 +100,7 @@ Just add a Service annotation to your service implementation: Service annotations mandatory value must declare the SPI you want the service class to be registered to. All other annotation attributes are optional. -- id defines a custom id which can be used to locate a specific servics implementation via the generated service locator class. Defaults to fully qualified service class name in generated service locator. +- id defines a custom id which can be used to locate a specific services implementation via the generated service locator class. Defaults to fully qualified service class name in generated service locator. - description declares a short description about the implementation - priority is used to define a specific order in which the services are located From e11bd5c53aadaebef3c8b65752fc23ded117c4d6 Mon Sep 17 00:00:00 2001 From: Fishermans Date: Mon, 16 May 2022 16:11:58 +0200 Subject: [PATCH 6/6] bump jdk to version 8 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ae477b3..097481c 100644 --- a/pom.xml +++ b/pom.xml @@ -76,10 +76,10 @@ UTF-8 UTF-8 - 1.7 + 1.8 - 1.7 - 1.7 + 1.8 + 1.8 0.9.0