From 074168dea8ea4107e14b9e788eca8bb3f006a50a Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 24 Mar 2020 18:17:03 +0100 Subject: [PATCH] refactor(liquibase): record service classes implementation instead of generating a native resource file for each service interface This cleans up the extension processing part a little bit as we can record these info in a registry and do a map lookup during runtime instead of reading the generated resource file for each service class --- .../quarkus/liquibase/LiquibaseProcessor.java | 53 ++++++++------- .../liquibase/runtime/LiquibaseRecorder.java | 7 ++ .../runtime/graal/LiquibaseServiceLoader.java | 67 +++++++------------ .../graal/SubstituteServiceLocator.java | 4 +- 4 files changed, 60 insertions(+), 71 deletions(-) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/LiquibaseProcessor.java index f165a6c1ed4df..12b6b5399b457 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/LiquibaseProcessor.java @@ -1,15 +1,14 @@ package io.quarkus.liquibase; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import static io.quarkus.liquibase.runtime.graal.LiquibaseServiceLoader.serviceResourceFile; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -63,7 +62,9 @@ CapabilityBuildItem capability() { } @BuildStep(onlyIf = NativeBuild.class) + @Record(STATIC_INIT) void nativeImageConfiguration( + LiquibaseRecorder recorder, List jdbcDataSourceBuildItems, BuildProducer reflective, BuildProducer resource, @@ -96,6 +97,8 @@ void nativeImageConfiguration( liquibase.sql.visitor.SqlVisitor.class); // load the liquibase services + Map> serviceClassesImplementationRegistry = new HashMap<>(); + Stream.of(liquibase.diff.compare.DatabaseObjectComparator.class, liquibase.parser.NamespaceDetails.class, liquibase.precondition.Precondition.class, @@ -108,9 +111,11 @@ void nativeImageConfiguration( liquibase.executor.Executor.class, liquibase.lockservice.LockService.class, liquibase.sqlgenerator.SqlGenerator.class) - .forEach(t -> addService(reflective, generatedResource, resource, t, true)); + .forEach(t -> addService(reflective, t, true, serviceClassesImplementationRegistry)); - addService(reflective, generatedResource, resource, liquibase.license.LicenseService.class, false); + addService(reflective, liquibase.license.LicenseService.class, false, serviceClassesImplementationRegistry); + + recorder.setServicesImplementations(serviceClassesImplementationRegistry); Collection dataSourceNames = jdbcDataSourceBuildItems.stream() .map(i -> i.getName()) @@ -179,28 +184,26 @@ ServiceStartBuildItem configureRuntimeProperties(LiquibaseRecorder recorder, /** * Search for all implementation of the interface {@code className}. *

- * The service interface is added to the reflection configuration. - * Each implementation is added to the reflection configuration and added to a text file - * which contains all the implementation classes. - * This text file is added to the native resources and is used to load the implementations - * of the service. + * Each implementation is added to the reflection configuration and recorded + * in a map which used to load the implementations of the service in native image. */ - private void addService(BuildProducer reflective, - BuildProducer generatedResourceProducer, - BuildProducer resourceProducer, - Class className, boolean methods) { - - Class[] impl = ServiceLocator.getInstance().findClasses(className); - if (impl != null && impl.length > 0) { - reflective.produce(new ReflectiveClassBuildItem(true, methods, false, impl)); - String resourcesList = Arrays.stream(impl) - .map(Class::getName) - .collect(Collectors.joining("\n", "", "\n")); - String resourceName = serviceResourceFile(className); - generatedResourceProducer.produce( - new GeneratedResourceBuildItem(resourceName, resourcesList.getBytes(StandardCharsets.UTF_8))); - resourceProducer.produce(new NativeImageResourceBuildItem(resourceName)); + private void addService(BuildProducer reflective, Class className, boolean methods, + Map> serviceClassesImplementationRegistry) { + + Class[] classImplementations = ServiceLocator.getInstance().findClasses(className); + + if (classImplementations != null && classImplementations.length > 0) { + reflective.produce(new ReflectiveClassBuildItem(true, methods, false, classImplementations)); + List serviceImplementations = new ArrayList<>(); + + for (Class classImpl : classImplementations) { + serviceImplementations.add(classImpl.getName()); + } + + serviceClassesImplementationRegistry.put(className.getName(), serviceImplementations); } + + reflective.produce(new ReflectiveClassBuildItem(false, false, false, className.getName())); reflective.produce(new ReflectiveClassBuildItem(false, false, false, className.getName())); } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java index 61dab93a8a61d..b2f7c60e1db74 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java @@ -1,6 +1,8 @@ package io.quarkus.liquibase.runtime; import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; import javax.enterprise.inject.Default; @@ -10,6 +12,7 @@ import io.quarkus.arc.runtime.BeanContainerListener; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.liquibase.runtime.graal.LiquibaseServiceLoader; import io.quarkus.runtime.annotations.Recorder; import liquibase.Liquibase; import liquibase.exception.LiquibaseException; @@ -20,6 +23,10 @@ @Recorder public class LiquibaseRecorder { + public void setServicesImplementations(Map> serviceLoader) { + LiquibaseServiceLoader.setServicesImplementations(serviceLoader); + } + /** * Sets the liquibase build configuration * diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/LiquibaseServiceLoader.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/LiquibaseServiceLoader.java index 5d1efe3293cce..feca066babf7c 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/LiquibaseServiceLoader.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/LiquibaseServiceLoader.java @@ -1,67 +1,46 @@ package io.quarkus.liquibase.runtime.graal; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.jboss.logging.Logger; +import java.util.Map; /** * The liquibase service class loader for native image. * * The liquibase extension has its own implementation of the class-path scanner to load the Services (Interface implementation). - * For this we generate .txt files with the list of implementation classes at build time which are used in the native runtime. */ public class LiquibaseServiceLoader { - private static final Logger LOGGER = Logger.getLogger(LiquibaseServiceLoader.class); - - /** - * File prefix with the service implementation classes list. It is generated dynamically in the Liquibase Quarkus Processor. - */ - private final static String SERVICES_IMPL = "META-INF/liquibase/"; + private static Map> servicesImplementations; /** - * The service implementation classes list resource name - * - * @param requiredInterface the required interface - * @return the resource file in the class-path - */ - public static String serviceResourceFile(Class requiredInterface) { - return SERVICES_IMPL + requiredInterface.getName() + ".txt"; - } - - /** - * Finds all classes for the required interface. The list of classes will be load from the txt files - * which are generated in the build phase. + * Finds all classes for the required interface. * * @param requiredInterface the required interface * @return the list of the classes that implements the required interface */ public static List> findClassesImpl(Class requiredInterface) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - String resourceName = serviceResourceFile(requiredInterface); - LOGGER.debugf("Liquibase service resource file: %s", resourceName); - try (InputStream resource = classLoader.getResourceAsStream(resourceName); - BufferedReader reader = new BufferedReader( - new InputStreamReader(Objects.requireNonNull(resource), StandardCharsets.UTF_8))) { + List classesImplementationNames = servicesImplementations.get(requiredInterface.getName()); - return reader.lines().map(className -> { - try { - LOGGER.debugf("Loading liquibase class: %s", className); - return Class.forName(className); - } catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - }).collect(Collectors.toList()); - } catch (IOException e) { - throw new IllegalStateException(e); + if (classesImplementationNames == null || classesImplementationNames.isEmpty()) { + return Collections.emptyList(); } + List> classImplementations = new ArrayList<>(); + + for (String classImplementation : classesImplementationNames) { + try { + classImplementations.add(Class.forName(classImplementation)); + } catch (ClassNotFoundException exception) { + throw new IllegalStateException(exception); + } + } + + return classImplementations; + } + + public static void setServicesImplementations(Map> servicesImplementations) { + LiquibaseServiceLoader.servicesImplementations = servicesImplementations; } } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteServiceLocator.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteServiceLocator.java index 63bbfc87f2985..614e7dd5115c9 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteServiceLocator.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteServiceLocator.java @@ -8,13 +8,13 @@ /** * The liquibase service locator substitute replaces liquibase classpath scanner method * {@link liquibase.servicelocator.ServiceLocator#findClasses(Class)} with a custom implementation - * {@link LiquibaseServiceLoader#findClassesImpl(Class)} which used the prebuilt txt file. + * {@link LiquibaseServiceLoader#findClassesImpl(Class)}. */ @TargetClass(className = "liquibase.servicelocator.ServiceLocator") final class SubstituteServiceLocator { @Substitute - private List> findClassesImpl(Class requiredInterface) throws Exception { + private List> findClassesImpl(Class requiredInterface) { return LiquibaseServiceLoader.findClassesImpl(requiredInterface); }