From 8462888c12f51893ca7128418a3db47639fbf276 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Sun, 18 Feb 2024 11:42:23 -0500 Subject: [PATCH] feat: Spring boot native image support (#609) --- .github/workflows/pull_request.yml | 9 + spring-integration/pom.xml | 1 + .../spring-boot-autoconfigure/pom.xml | 12 +- .../TimefoldBenchmarkAutoConfiguration.java | 4 +- .../TimefoldSolverAotContribution.java | 49 + .../TimefoldSolverAotFactory.java | 42 + ...a => TimefoldSolverAutoConfiguration.java} | 282 ++-- .../TimefoldSolverBeanFactory.java | 210 +++ .../proxy-config.json | 8 + .../reflect-config.json | 801 ++++++++++++ .../resource-config.json | 9 + ...ot.autoconfigure.AutoConfiguration.imports | 5 +- ... TimefoldSolverAutoConfigurationTest.java} | 47 +- ...rMultipleSolverAutoConfigurationTest.java} | 37 +- .../spring-boot-integration-test/pom.xml | 148 +++ .../boot/it/TimefoldSolverController.java | 25 + .../boot/it/TimefoldSolverSpringBootApp.java | 12 + .../boot/it/domain/IntegrationTestEntity.java | 37 + .../it/domain/IntegrationTestSolution.java | 55 + .../boot/it/domain/IntegrationTestValue.java | 4 + .../IntegrationTestConstraintProvider.java | 19 + .../src/main/resources/application.properties | 1 + ...foldSolverTestResourceIntegrationTest.java | 72 ++ .../native-image/resource-config.json | 9 + .../src/test/resources/solver-full.xml | 1151 +++++++++++++++++ 25 files changed, 2815 insertions(+), 234 deletions(-) create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java rename spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/{TimefoldAutoConfiguration.java => TimefoldSolverAutoConfiguration.java} (73%) create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/proxy-config.json create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/resource-config.json rename spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/{TimefoldAutoConfigurationTest.java => TimefoldSolverAutoConfigurationTest.java} (94%) rename spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/{TimefoldMultipleSolverAutoConfigurationTest.java => TimefoldSolverMultipleSolverAutoConfigurationTest.java} (96%) create mode 100644 spring-integration/spring-boot-integration-test/pom.xml create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverController.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverSpringBootApp.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestEntity.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestSolution.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestValue.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/solver/IntegrationTestConstraintProvider.java create mode 100644 spring-integration/spring-boot-integration-test/src/main/resources/application.properties create mode 100644 spring-integration/spring-boot-integration-test/src/test/java/ai/timefold/solver/spring/boot/it/TimefoldSolverTestResourceIntegrationTest.java create mode 100644 spring-integration/spring-boot-integration-test/src/test/resources/META-INF/native-image/resource-config.json create mode 100644 spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c3df777cf6..e569ab4ccd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -43,6 +43,15 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '17' + distribution: 'graalvm-community' + set-java-home: 'false' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - uses: actions/setup-java@v4 with: java-version: '17' diff --git a/spring-integration/pom.xml b/spring-integration/pom.xml index 4cd5a11e65..0c5c30a031 100644 --- a/spring-integration/pom.xml +++ b/spring-integration/pom.xml @@ -26,6 +26,7 @@ spring-boot-autoconfigure spring-boot-starter + spring-boot-integration-test diff --git a/spring-integration/spring-boot-autoconfigure/pom.xml b/spring-integration/spring-boot-autoconfigure/pom.xml index 84f6cb8e61..0de50c0bb1 100644 --- a/spring-integration/spring-boot-autoconfigure/pom.xml +++ b/spring-integration/spring-boot-autoconfigure/pom.xml @@ -63,6 +63,13 @@ spring-boot-autoconfigure + + + com.fasterxml.jackson.core + jackson-databind + true + + org.springframework.boot spring-boot-configuration-processor @@ -78,11 +85,6 @@ spring-web true - - com.fasterxml.jackson.core - jackson-databind - true - diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java index 9c58c92870..ea118c9f90 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java @@ -27,7 +27,7 @@ import org.springframework.context.annotation.Lazy; @Configuration -@AutoConfigureAfter(TimefoldAutoConfiguration.class) +@AutoConfigureAfter(TimefoldSolverAutoConfiguration.class) @ConditionalOnClass({ PlannerBenchmarkFactory.class }) @ConditionalOnMissingBean({ PlannerBenchmarkFactory.class }) @EnableConfigurationProperties({ TimefoldProperties.class }) @@ -88,7 +88,7 @@ public PlannerBenchmarkConfig plannerBenchmarkConfig() { } if (timefoldProperties.getBenchmark() != null && timefoldProperties.getBenchmark().getSolver() != null) { - TimefoldAutoConfiguration + TimefoldSolverAutoConfiguration .applyTerminationProperties(benchmarkConfig.getInheritedSolverBenchmarkConfig().getSolverConfig(), timefoldProperties.getBenchmark().getSolver().getTermination()); } diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java new file mode 100644 index 0000000000..e88b533c10 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java @@ -0,0 +1,49 @@ +package ai.timefold.solver.spring.boot.autoconfigure; + +import java.util.Map; + +import ai.timefold.solver.core.config.solver.SolverConfig; + +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; + +public class TimefoldSolverAotContribution implements BeanFactoryInitializationAotContribution { + private final Map solverConfigMap; + + public TimefoldSolverAotContribution(Map solverConfigMap) { + this.solverConfigMap = solverConfigMap; + } + + /** + * Register a type for reflection, allowing introspection + * of its members at runtime in a native build. + */ + private static void registerType(ReflectionHints reflectionHints, Class type) { + reflectionHints.registerType(type, + MemberCategory.INTROSPECT_PUBLIC_METHODS, + MemberCategory.INTROSPECT_DECLARED_METHODS, + MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, + MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, + MemberCategory.PUBLIC_FIELDS, + MemberCategory.DECLARED_FIELDS, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.INVOKE_PUBLIC_METHODS); + } + + @Override + public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection(); + for (SolverConfig solverConfig : solverConfigMap.values()) { + solverConfig.visitReferencedClasses(type -> { + if (type != null) { + registerType(reflectionHints, type); + } + }); + } + } +} diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java new file mode 100644 index 0000000000..5087078c25 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java @@ -0,0 +1,42 @@ +package ai.timefold.solver.spring.boot.autoconfigure; + +import java.io.StringReader; + +import ai.timefold.solver.core.api.solver.SolverFactory; +import ai.timefold.solver.core.api.solver.SolverManager; +import ai.timefold.solver.core.config.solver.SolverConfig; +import ai.timefold.solver.core.config.solver.SolverManagerConfig; +import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO; +import ai.timefold.solver.spring.boot.autoconfigure.config.SolverManagerProperties; +import ai.timefold.solver.spring.boot.autoconfigure.config.TimefoldProperties; + +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; + +public class TimefoldSolverAotFactory implements EnvironmentAware { + private TimefoldProperties timefoldProperties; + + @Override + public void setEnvironment(Environment environment) { + // We need the environment to set run time properties of SolverFactory and SolverManager + BindResult result = Binder.get(environment).bind("timefold", TimefoldProperties.class); + this.timefoldProperties = result.orElseGet(TimefoldProperties::new); + } + + public SolverManager solverManagerSupplier(String solverConfigXml) { + SolverFactory solverFactory = SolverFactory.create(solverConfigSupplier(solverConfigXml)); + SolverManagerConfig solverManagerConfig = new SolverManagerConfig(); + SolverManagerProperties solverManagerProperties = timefoldProperties.getSolverManager(); + if (solverManagerProperties != null && solverManagerProperties.getParallelSolverCount() != null) { + solverManagerConfig.setParallelSolverCount(solverManagerProperties.getParallelSolverCount()); + } + return SolverManager.create(solverFactory, solverManagerConfig); + } + + public SolverConfig solverConfigSupplier(String solverConfigXml) { + SolverConfigIO solverConfigIO = new SolverConfigIO(); + return solverConfigIO.read(new StringReader(solverConfigXml)); + } +} diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java similarity index 73% rename from spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfiguration.java rename to spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java index dfed6a4df5..24284b5aae 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.joining; +import java.io.StringWriter; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.HashMap; @@ -10,7 +11,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.BiFunction; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.entity.PlanningPin; @@ -25,38 +25,31 @@ import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable; import ai.timefold.solver.core.api.domain.variable.ShadowVariable; -import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.ScoreManager; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator; -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; import ai.timefold.solver.core.api.score.stream.ConstraintProvider; -import ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType; import ai.timefold.solver.core.api.solver.SolutionManager; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.SolverManager; import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; import ai.timefold.solver.core.config.solver.SolverConfig; -import ai.timefold.solver.core.config.solver.SolverManagerConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; -import ai.timefold.solver.jackson.api.TimefoldJacksonModule; -import ai.timefold.solver.spring.boot.autoconfigure.config.SolverManagerProperties; +import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO; import ai.timefold.solver.spring.boot.autoconfigure.config.SolverProperties; import ai.timefold.solver.spring.boot.autoconfigure.config.TerminationProperties; import ai.timefold.solver.spring.boot.autoconfigure.config.TimefoldProperties; -import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; -import ai.timefold.solver.test.api.score.stream.MultiConstraintVerification; -import ai.timefold.solver.test.api.score.stream.SingleConstraintVerification; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -67,23 +60,19 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import com.fasterxml.jackson.databind.Module; - -@Configuration +@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ SolverConfig.class, SolverFactory.class, ScoreManager.class, SolutionManager.class, SolverManager.class }) @ConditionalOnMissingBean({ SolverConfig.class, SolverFactory.class, ScoreManager.class, SolutionManager.class, SolverManager.class }) @EnableConfigurationProperties({ TimefoldProperties.class }) -public class TimefoldAutoConfiguration - implements BeanClassLoaderAware, ApplicationContextAware, EnvironmentAware, BeanFactoryPostProcessor { +public class TimefoldSolverAutoConfiguration + implements BeanClassLoaderAware, ApplicationContextAware, EnvironmentAware, BeanFactoryInitializationAotProcessor, + BeanDefinitionRegistryPostProcessor { - private static final Log LOG = LogFactory.getLog(TimefoldAutoConfiguration.class); + private static final Log LOG = LogFactory.getLog(TimefoldSolverAutoConfiguration.class); private static final String DEFAULT_SOLVER_CONFIG_NAME = "getSolverConfig"; private static final Class[] PLANNING_ENTITY_FIELD_ANNOTATIONS = new Class[] { PlanningPin.class, @@ -102,7 +91,7 @@ public class TimefoldAutoConfiguration private ClassLoader beanClassLoader; private TimefoldProperties timefoldProperties; - protected TimefoldAutoConfiguration() { + protected TimefoldSolverAutoConfiguration() { } @Override @@ -123,8 +112,7 @@ public void setEnvironment(Environment environment) { this.timefoldProperties = result.orElseGet(TimefoldProperties::new); } - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + private Map getSolverConfigMap() { IncludeAbstractClassesEntityScanner entityScanner = new IncludeAbstractClassesEntityScanner(this.context); if (!entityScanner.hasSolutionOrEntityClasses()) { LOG.warn( @@ -134,8 +122,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) Maybe move your planning solution and entity classes to your application class's (sub)package (or use @%s).""" .formatted(PlanningSolution.class.getSimpleName(), PlanningEntity.class.getSimpleName(), SpringBootApplication.class.getSimpleName(), EntityScan.class.getSimpleName())); - beanFactory.registerSingleton(DEFAULT_SOLVER_CONFIG_NAME, new SolverConfig(beanClassLoader)); - return; + return Map.of(); } Map solverConfigMap = new HashMap<>(); // Step 1 - create all SolverConfig @@ -156,20 +143,53 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) // Step 3 - load all additional information per SolverConfig solverConfigMap.forEach( (solverName, solverConfig) -> loadSolverConfig(entityScanner, timefoldProperties, solverName, solverConfig)); + return solverConfigMap; + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + Map solverConfigMap = getSolverConfigMap(); + return new TimefoldSolverAotContribution(solverConfigMap); + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + Map solverConfigMap = getSolverConfigMap(); + SolverConfigIO solverConfigIO = new SolverConfigIO(); + registry.registerBeanDefinition(TimefoldSolverAotFactory.class.getName(), + new RootBeanDefinition(TimefoldSolverAotFactory.class)); + if (solverConfigMap.isEmpty()) { + RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(SolverConfig.class); + rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryMethodName("solverConfigSupplier"); + StringWriter solverXmlOutput = new StringWriter(); + solverConfigIO.write(new SolverConfig(), solverXmlOutput); + rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue( + solverXmlOutput.toString()); + registry.registerBeanDefinition(DEFAULT_SOLVER_CONFIG_NAME, rootBeanDefinition); + return; + } if (timefoldProperties.getSolver() == null || timefoldProperties.getSolver().size() == 1) { - beanFactory.registerSingleton(DEFAULT_SOLVER_CONFIG_NAME, solverConfigMap.values().iterator().next()); + RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(SolverConfig.class); + rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryMethodName("solverConfigSupplier"); + StringWriter solverXmlOutput = new StringWriter(); + solverConfigIO.write(solverConfigMap.values().iterator().next(), solverXmlOutput); + rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue( + solverXmlOutput.toString()); + registry.registerBeanDefinition(DEFAULT_SOLVER_CONFIG_NAME, rootBeanDefinition); } else { // Only SolverManager can be injected for multiple solver configurations solverConfigMap.forEach((solverName, solverConfig) -> { - SolverFactory solverFactory = SolverFactory.create(solverConfig); - - SolverManagerConfig solverManagerConfig = new SolverManagerConfig(); - SolverManagerProperties solverManagerProperties = timefoldProperties.getSolverManager(); - if (solverManagerProperties != null && solverManagerProperties.getParallelSolverCount() != null) { - solverManagerConfig.setParallelSolverCount(solverManagerProperties.getParallelSolverCount()); - } - beanFactory.registerSingleton(solverName, SolverManager.create(solverFactory, solverManagerConfig)); + RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(SolverManager.class); + rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryMethodName("solverManagerSupplier"); + StringWriter solverXmlOutput = new StringWriter(); + solverConfigIO.write(solverConfig, solverXmlOutput); + rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue( + solverXmlOutput.toString()); + registry.registerBeanDefinition(solverName, rootBeanDefinition); }); } } @@ -256,7 +276,8 @@ protected void applyScoreDirectorFactoryProperties(IncludeAbstractClassesEntityS } } - private ScoreDirectorFactoryConfig defaultScoreDirectoryFactoryConfig(IncludeAbstractClassesEntityScanner entityScanner) { + private static ScoreDirectorFactoryConfig + defaultScoreDirectoryFactoryConfig(IncludeAbstractClassesEntityScanner entityScanner) { ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig(); scoreDirectorFactoryConfig .setEasyScoreCalculatorClass(entityScanner.findFirstImplementingClass(EasyScoreCalculator.class)); @@ -287,14 +308,7 @@ static void applyTerminationProperties(SolverConfig solverConfig, TerminationPro } } - private void failInjectionWithMultipleSolvers(String resourceName) { - if (timefoldProperties.getSolver() != null && timefoldProperties.getSolver().size() > 1) { - throw new BeanCreationException( - "No qualifying bean of type '%s' available".formatted(resourceName)); - } - } - - private void assertNoMemberAnnotationWithoutClassAnnotation(IncludeAbstractClassesEntityScanner entityScanner) { + private static void assertNoMemberAnnotationWithoutClassAnnotation(IncludeAbstractClassesEntityScanner entityScanner) { List> timefoldFieldAnnotationList = entityScanner.findClassesWithAnnotation(PLANNING_ENTITY_FIELD_ANNOTATIONS); List> entityList = entityScanner.findEntityClassList(); @@ -311,7 +325,7 @@ The classes ([%s]) do not have the %s annotation, even though they contain prope } } - private void assertSolverConfigSolutionClasses(IncludeAbstractClassesEntityScanner entityScanner, + private static void assertSolverConfigSolutionClasses(IncludeAbstractClassesEntityScanner entityScanner, Map solverConfigMap) { // Validate the solution class // No solution class @@ -365,7 +379,7 @@ Some solver configs (%s) don't specify a %s class, yet there are multiple availa } } - private void assertSolverConfigEntityClasses(IncludeAbstractClassesEntityScanner entityScanner) { + private static void assertSolverConfigEntityClasses(IncludeAbstractClassesEntityScanner entityScanner) { // No Entity class String emptyListErrorMessage = """ No classes were found with a @%s annotation. @@ -378,7 +392,7 @@ private void assertSolverConfigEntityClasses(IncludeAbstractClassesEntityScanner assertTargetClasses(entityScanner.findEntityClassList(), PlanningEntity.class.getSimpleName()); } - private void assertSolverConfigConstraintClasses( + private static void assertSolverConfigConstraintClasses( IncludeAbstractClassesEntityScanner entityScanner, Map solverConfigMap) { List> simpleScoreClassList = entityScanner.findImplementingClassList(EasyScoreCalculator.class); @@ -386,7 +400,7 @@ private void assertSolverConfigConstraintClasses( entityScanner.findImplementingClassList(ConstraintProvider.class); List> incrementalScoreClassList = entityScanner.findImplementingClassList(IncrementalScoreCalculator.class); - // No score classes + // No score calculators if (simpleScoreClassList.isEmpty() && constraintScoreClassList.isEmpty() && incrementalScoreClassList.isEmpty()) { throw new IllegalStateException( @@ -399,8 +413,17 @@ private void assertSolverConfigConstraintClasses( ConstraintProvider.class.getSimpleName(), SpringBootApplication.class.getSimpleName(), ConstraintProvider.class.getSimpleName(), EntityScan.class.getSimpleName())); } - // Multiple classes and single solver - String errorMessage = "Multiple score classes classes (%s) that implements %s were found in the classpath."; + assertSolverConfigsSpecifyScoreCalculatorWhenAmbigious(solverConfigMap, simpleScoreClassList, constraintScoreClassList, + incrementalScoreClassList); + assertNoUnusedScoreClasses(solverConfigMap, simpleScoreClassList, constraintScoreClassList, incrementalScoreClassList); + } + + private static void assertSolverConfigsSpecifyScoreCalculatorWhenAmbigious(Map solverConfigMap, + List> simpleScoreClassList, + List> constraintScoreClassList, + List> incrementalScoreClassList) { + // Single solver, multiple score calculators + String errorMessage = "Multiple score calculator classes (%s) that implements %s were found in the classpath."; if (simpleScoreClassList.size() > 1 && solverConfigMap.size() == 1) { throw new IllegalStateException(errorMessage.formatted( simpleScoreClassList.stream().map(Class::getSimpleName).collect(joining(", ")), @@ -416,11 +439,12 @@ private void assertSolverConfigConstraintClasses( incrementalScoreClassList.stream().map(Class::getSimpleName).collect(joining(", ")), IncrementalScoreCalculator.class.getSimpleName())); } - // Multiple classes and at least one solver config does not specify the score class - errorMessage = """ - Some solver configs (%s) don't specify a %s score class, yet there are multiple available (%s) on the classpath. - Maybe set the XML config file to the related solver configs, or add the missing score classes to the XML files, - or remove the unnecessary score classes from the classpath."""; + // Multiple solvers, multiple score calculators + errorMessage = + """ + Some solver configs (%s) don't specify a %s score calculator class, yet there are multiple available (%s) on the classpath. + Maybe set the XML config file to the related solver configs, or add the missing score calculator to the XML files, + or remove the unnecessary score calculator from the classpath."""; List solverConfigWithoutConstraintClassList = solverConfigMap.entrySet().stream() .filter(e -> e.getValue().getScoreDirectorFactoryConfig() == null || e.getValue().getScoreDirectorFactoryConfig().getEasyScoreCalculatorClass() == null) @@ -454,7 +478,13 @@ Some solver configs (%s) don't specify a %s score class, yet there are multiple IncrementalScoreCalculator.class.getSimpleName(), incrementalScoreClassList.stream().map(Class::getSimpleName).collect(joining(", ")))); } - // Unused score classes + } + + private static void assertNoUnusedScoreClasses(Map solverConfigMap, + List> simpleScoreClassList, + List> constraintScoreClassList, + List> incrementalScoreClassList) { + String errorMessage; List solverConfigWithUnusedSolutionClassList = simpleScoreClassList.stream() .map(Class::getName) .filter(className -> solverConfigMap.values().stream() @@ -494,7 +524,8 @@ Some solver configs (%s) don't specify a %s score class, yet there are multiple } } - private void assertEmptyInstances(IncludeAbstractClassesEntityScanner entityScanner, Class clazz, + private static void assertEmptyInstances(IncludeAbstractClassesEntityScanner entityScanner, + Class clazz, String errorMessage) { try { Collection> classInstanceCollection = entityScanner.scan(clazz); @@ -506,7 +537,7 @@ private void assertEmptyInstances(IncludeAbstractClassesEntityScanner entityScan } } - private void assertTargetClasses(Collection> targetCollection, String targetAnnotation) { + private static void assertTargetClasses(Collection> targetCollection, String targetAnnotation) { List invalidClasses = targetCollection.stream() .filter(target -> target.isRecord() || target.isEnum() || target.isPrimitive()) .map(Class::getSimpleName) @@ -518,135 +549,4 @@ private void assertTargetClasses(Collection> targetCollection, String t targetAnnotation)); } } - - @Bean - @Lazy - public TimefoldSolverBannerBean getBanner() { - return new TimefoldSolverBannerBean(); - } - - @Bean - @Lazy - @ConditionalOnMissingBean - public SolverFactory getSolverFactory() { - failInjectionWithMultipleSolvers(SolverFactory.class.getName()); - SolverConfig solverConfig = context.getBean(SolverConfig.class); - if (solverConfig == null || solverConfig.getSolutionClass() == null) { - return null; - } - return SolverFactory.create(solverConfig); - } - - @Bean - @Lazy - @ConditionalOnMissingBean - public SolverManager solverManager(SolverFactory solverFactory) { - // TODO supply ThreadFactory - if (solverFactory == null) { - return null; - } - SolverManagerConfig solverManagerConfig = new SolverManagerConfig(); - SolverManagerProperties solverManagerProperties = timefoldProperties.getSolverManager(); - if (solverManagerProperties != null && solverManagerProperties.getParallelSolverCount() != null) { - solverManagerConfig.setParallelSolverCount(solverManagerProperties.getParallelSolverCount()); - } - return SolverManager.create(solverFactory, solverManagerConfig); - } - - @Bean - @Lazy - @ConditionalOnMissingBean - @Deprecated(forRemoval = true) - public > ScoreManager scoreManager() { - failInjectionWithMultipleSolvers(ScoreManager.class.getName()); - SolverFactory solverFactory = context.getBean(SolverFactory.class); - if (solverFactory == null) { - return null; - } - return ScoreManager.create(solverFactory); - } - - @Bean - @Lazy - @ConditionalOnMissingBean - public > SolutionManager solutionManager() { - failInjectionWithMultipleSolvers(SolutionManager.class.getName()); - SolverFactory solverFactory = context.getBean(SolverFactory.class); - if (solverFactory == null) { - return null; - } - return SolutionManager.create(solverFactory); - } - - // @Bean wrapped by static class to avoid classloading issues if dependencies are absent - @ConditionalOnClass({ ConstraintVerifier.class }) - @ConditionalOnMissingBean({ ConstraintVerifier.class }) - @AutoConfigureAfter(TimefoldAutoConfiguration.class) - class TimefoldConstraintVerifierConfiguration { - - private final ApplicationContext context; - - protected TimefoldConstraintVerifierConfiguration(ApplicationContext context) { - this.context = context; - } - - @Bean - @Lazy - @SuppressWarnings("unchecked") - - ConstraintVerifier constraintVerifier() { - // Using SolverConfig as an injected parameter here leads to an injection failure on an empty app, - // so we need to get the SolverConfig from context - failInjectionWithMultipleSolvers(ConstraintProvider.class.getName()); - SolverConfig solverConfig; - try { - solverConfig = context.getBean(SolverConfig.class); - } catch (BeansException exception) { - solverConfig = null; - } - - ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = - (solverConfig != null) ? solverConfig.getScoreDirectorFactoryConfig() : null; - if (scoreDirectorFactoryConfig == null || scoreDirectorFactoryConfig.getConstraintProviderClass() == null) { - // Return a mock ConstraintVerifier so not having ConstraintProvider doesn't crash tests - // (Cannot create custom condition that checks SolverConfig, since that - // requires TimefoldAutoConfiguration to have a no-args constructor) - final String noConstraintProviderErrorMsg = (scoreDirectorFactoryConfig != null) - ? "Cannot provision a ConstraintVerifier because there is no ConstraintProvider class." - : "Cannot provision a ConstraintVerifier because there is no PlanningSolution or PlanningEntity classes."; - return new ConstraintVerifier<>() { - @Override - public ConstraintVerifier - withConstraintStreamImplType(ConstraintStreamImplType constraintStreamImplType) { - throw new UnsupportedOperationException(noConstraintProviderErrorMsg); - } - - @Override - public SingleConstraintVerification - verifyThat(BiFunction constraintFunction) { - throw new UnsupportedOperationException(noConstraintProviderErrorMsg); - } - - @Override - public MultiConstraintVerification verifyThat() { - throw new UnsupportedOperationException(noConstraintProviderErrorMsg); - } - }; - } - - return ConstraintVerifier.create(solverConfig); - } - } - - // @Bean wrapped by static class to avoid classloading issues if dependencies are absent - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass({ Jackson2ObjectMapperBuilder.class, Score.class }) - static class TimefoldJacksonConfiguration { - - @Bean - Module jacksonModule() { - return TimefoldJacksonModule.createModule(); - } - - } } diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java new file mode 100644 index 0000000000..ff5411f0c8 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java @@ -0,0 +1,210 @@ +package ai.timefold.solver.spring.boot.autoconfigure; + +import java.util.function.BiFunction; + +import ai.timefold.solver.core.api.score.Score; +import ai.timefold.solver.core.api.score.ScoreManager; +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType; +import ai.timefold.solver.core.api.solver.SolutionManager; +import ai.timefold.solver.core.api.solver.SolverFactory; +import ai.timefold.solver.core.api.solver.SolverManager; +import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; +import ai.timefold.solver.core.config.solver.SolverConfig; +import ai.timefold.solver.core.config.solver.SolverManagerConfig; +import ai.timefold.solver.jackson.api.TimefoldJacksonModule; +import ai.timefold.solver.spring.boot.autoconfigure.config.SolverManagerProperties; +import ai.timefold.solver.spring.boot.autoconfigure.config.TimefoldProperties; +import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; +import ai.timefold.solver.test.api.score.stream.MultiConstraintVerification; +import ai.timefold.solver.test.api.score.stream.SingleConstraintVerification; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +import com.fasterxml.jackson.databind.Module; + +/** + * Must be seperated from {@link TimefoldSolverAutoConfiguration} since + * {@link TimefoldSolverAutoConfiguration} will not be available at runtime + * for a native image (since it is a {@link BeanFactoryInitializationAotProcessor}/ + * {@link BeanFactoryPostProcessor}). + */ +@Configuration +public class TimefoldSolverBeanFactory implements ApplicationContextAware, EnvironmentAware { + private ApplicationContext context; + private TimefoldProperties timefoldProperties; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void setEnvironment(Environment environment) { + // We need the environment to set run time properties of SolverFactory and SolverManager + BindResult result = Binder.get(environment).bind("timefold", TimefoldProperties.class); + this.timefoldProperties = result.orElseGet(TimefoldProperties::new); + } + + private void failInjectionWithMultipleSolvers(String resourceName) { + if (timefoldProperties.getSolver() != null && timefoldProperties.getSolver().size() > 1) { + throw new BeanCreationException( + "No qualifying bean of type '%s' available".formatted(resourceName)); + } + } + + @Bean + @Lazy + public TimefoldSolverBannerBean getBanner() { + return new TimefoldSolverBannerBean(); + } + + @Bean + @Lazy + @ConditionalOnMissingBean + public SolverFactory getSolverFactory() { + failInjectionWithMultipleSolvers(SolverFactory.class.getName()); + SolverConfig solverConfig = context.getBean(SolverConfig.class); + if (solverConfig.getSolutionClass() == null) { + return null; + } + return SolverFactory.create(solverConfig); + } + + @Bean + @Lazy + @ConditionalOnMissingBean + public SolverManager solverManager(SolverFactory solverFactory) { + // TODO supply ThreadFactory + if (solverFactory == null) { + return null; + } + SolverManagerConfig solverManagerConfig = new SolverManagerConfig(); + SolverManagerProperties solverManagerProperties = timefoldProperties.getSolverManager(); + if (solverManagerProperties != null && solverManagerProperties.getParallelSolverCount() != null) { + solverManagerConfig.setParallelSolverCount(solverManagerProperties.getParallelSolverCount()); + } + return SolverManager.create(solverFactory, solverManagerConfig); + } + + @Bean + @Lazy + @ConditionalOnMissingBean + @Deprecated(forRemoval = true) + /** + * @deprecated Use {@link SolutionManager} instead. + */ + public > ScoreManager scoreManager() { + failInjectionWithMultipleSolvers(ScoreManager.class.getName()); + SolverFactory solverFactory = context.getBean(SolverFactory.class); + return ScoreManager.create(solverFactory); + } + + @Bean + @Lazy + @ConditionalOnMissingBean + public > SolutionManager solutionManager() { + failInjectionWithMultipleSolvers(SolutionManager.class.getName()); + SolverFactory solverFactory = context.getBean(SolverFactory.class); + return SolutionManager.create(solverFactory); + } + + // @Bean wrapped by static class to avoid classloading issues if dependencies are absent + @ConditionalOnClass({ ConstraintVerifier.class }) + @ConditionalOnMissingBean({ ConstraintVerifier.class }) + @AutoConfigureAfter(TimefoldSolverAutoConfiguration.class) + class TimefoldConstraintVerifierConfiguration { + + private final ApplicationContext context; + + protected TimefoldConstraintVerifierConfiguration(ApplicationContext context) { + this.context = context; + } + + private static class UnsupportedConstraintVerifier + implements ConstraintVerifier { + final String errorMessage; + + public UnsupportedConstraintVerifier(String errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public ConstraintVerifier + withConstraintStreamImplType(ConstraintStreamImplType constraintStreamImplType) { + throw new UnsupportedOperationException(errorMessage); + } + + @Override + public SingleConstraintVerification + verifyThat(BiFunction constraintFunction) { + throw new UnsupportedOperationException(errorMessage); + } + + @Override + public MultiConstraintVerification verifyThat() { + throw new UnsupportedOperationException(errorMessage); + } + } + + @Bean + @Lazy + @SuppressWarnings("unchecked") + + ConstraintVerifier constraintVerifier() { + // Using SolverConfig as an injected parameter here leads to an injection failure on an empty app, + // so we need to get the SolverConfig from context + failInjectionWithMultipleSolvers(ConstraintProvider.class.getName()); + SolverConfig solverConfig; + try { + solverConfig = context.getBean(SolverConfig.class); + } catch (BeansException exception) { + solverConfig = null; + } + + ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = + (solverConfig != null) ? solverConfig.getScoreDirectorFactoryConfig() : null; + if (scoreDirectorFactoryConfig == null || scoreDirectorFactoryConfig.getConstraintProviderClass() == null) { + // Return a mock ConstraintVerifier so not having ConstraintProvider doesn't crash tests + // (Cannot create custom condition that checks SolverConfig, since that + // requires TimefoldSolverAutoConfiguration to have a no-args constructor) + final String noConstraintProviderErrorMsg = (scoreDirectorFactoryConfig != null) + ? "Cannot provision a ConstraintVerifier because there is no ConstraintProvider class." + : "Cannot provision a ConstraintVerifier because there is no PlanningSolution or PlanningEntity classes."; + return new UnsupportedConstraintVerifier<>(noConstraintProviderErrorMsg); + } + + return ConstraintVerifier.create(solverConfig); + } + } + + // @Bean wrapped by static class to avoid classloading issues if dependencies are absent + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Jackson2ObjectMapperBuilder.class, Score.class }) + static class TimefoldJacksonConfiguration { + + @Bean + Module jacksonModule() { + return TimefoldJacksonModule.createModule(); + } + + } +} diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/proxy-config.json b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/proxy-config.json new file mode 100644 index 0000000000..67b84579d7 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/proxy-config.json @@ -0,0 +1,8 @@ +[ + { + "interfaces": [ + "java.lang.Deprecated", + "org.glassfish.jaxb.core.v2.model.annotation.Locatable" + ] + } +] \ No newline at end of file diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json new file mode 100644 index 0000000000..12579138e6 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json @@ -0,0 +1,801 @@ +[ + { + "name": "java.util.ArrayList", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbCustomPropertiesAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbDurationAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbLocaleAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbOffsetDateTimeAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.api.domain.common.DomainAccessType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.AbstractConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicForagerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicPickEarlyType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.placer.EntityPlacerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.placer.PooledEntityPlacerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.exhaustivesearch.NodeExplorationType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.SelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionDistributionType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.entity.pillar.PillarSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.list.DestinationSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.composite.CartesianProductMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.factory.MoveIteratorFactoryConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.factory.MoveListFactoryConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.AbstractPillarMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarChangeMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarSwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.SubPillarType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.SubChainChangeMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.SubChainSwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListChangeMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListSwapMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.heuristic.selector.value.chained.SubChainSelectorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.localsearch.LocalSearchType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.acceptor.AcceptorType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.acceptor.LocalSearchAcceptorConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.acceptor.stepcountinghillclimbing.StepCountingHillClimbingType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.forager.FinalistPodiumType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.forager.LocalSearchForagerConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.localsearch.decider.forager.LocalSearchPickEarlyType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.phase.NoChangePhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.phase.PhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.solver.EnvironmentMode", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.solver.SolverConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.config.solver.monitoring.SolverMetric", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.solver.random.RandomType", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.solver.termination.TerminationCompositionStyle", + "allDeclaredFields": true + }, + { + "name": "ai.timefold.solver.core.config.solver.termination.TerminationConfig", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbCustomPropertiesAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbCustomPropertiesAdapter$JaxbAdaptedMap", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "ai.timefold.solver.core.impl.io.jaxb.adapter.JaxbCustomPropertiesAdapter$JaxbAdaptedMapEntry", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "jakarta.xml.bind.Binder" + }, + { + "name": "jakarta.xml.bind.annotation.XmlAccessorType", + "queryAllDeclaredMethods": true + }, + { + "name": "jakarta.xml.bind.annotation.XmlElement", + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "type", + "parameterTypes": [] + } + ] + }, + { + "name": "jakarta.xml.bind.annotation.XmlElements", + "queryAllDeclaredMethods": true + }, + { + "name": "jakarta.xml.bind.annotation.XmlEnum", + "methods": [ + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "name": "jakarta.xml.bind.annotation.XmlRootElement", + "queryAllDeclaredMethods": true + }, + { + "name": "jakarta.xml.bind.annotation.XmlSeeAlso", + "methods": [ + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "name": "jakarta.xml.bind.annotation.XmlTransient", + "queryAllDeclaredMethods": true + }, + { + "name": "jakarta.xml.bind.annotation.XmlType", + "queryAllDeclaredMethods": true, + "methods": [ + { + "name": "factoryClass", + "parameterTypes": [] + } + ] + }, + { + "name": "jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter", + "methods": [ + { + "name": "type", + "parameterTypes": [] + }, + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "name": "org.glassfish.jaxb.core.v2.model.nav.ReflectionNavigator", + "methods": [ + { + "name": "getInstance", + "parameterTypes": [] + } + ] + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayElementLeafProperty", + "queryAllPublicConstructors": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl", + "org.glassfish.jaxb.runtime.v2.model.runtime.RuntimeElementPropertyInfo" + ] + } + ] + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayElementNodeProperty", + "queryAllPublicConstructors": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl", + "org.glassfish.jaxb.runtime.v2.model.runtime.RuntimeElementPropertyInfo" + ] + } + ] + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayReferenceNodeProperty", + "queryAllPublicConstructors": true + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.SingleElementLeafProperty", + "queryAllPublicConstructors": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl", + "org.glassfish.jaxb.runtime.v2.model.runtime.RuntimeElementPropertyInfo" + ] + } + ] + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.SingleElementNodeProperty", + "queryAllPublicConstructors": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl", + "org.glassfish.jaxb.runtime.v2.model.runtime.RuntimeElementPropertyInfo" + ] + } + ] + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.SingleMapNodeProperty", + "queryAllPublicConstructors": true + }, + { + "name": "org.glassfish.jaxb.runtime.v2.runtime.property.SingleReferenceNodeProperty", + "queryAllPublicConstructors": true + }, + { + "name": "org.glassfish.jersey.server.spring.SpringComponentProvider" + } +] \ No newline at end of file diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/resource-config.json b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/resource-config.json new file mode 100644 index 0000000000..d722763efd --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "solver.xsd" + } + ] + } +} \ No newline at end of file diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 10c855d5a8..4ab87107aa 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ -ai.timefold.solver.spring.boot.autoconfigure.TimefoldAutoConfiguration -ai.timefold.solver.spring.boot.autoconfigure.TimefoldBenchmarkAutoConfiguration \ No newline at end of file +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverAutoConfiguration +ai.timefold.solver.spring.boot.autoconfigure.TimefoldBenchmarkAutoConfiguration +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverBeanFactory \ No newline at end of file diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfigurationTest.java similarity index 94% rename from spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfigurationTest.java rename to spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfigurationTest.java index 5dc7e00b8a..85a1bb1c73 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldAutoConfigurationTest.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfigurationTest.java @@ -2,7 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; import java.util.Collections; @@ -68,7 +70,7 @@ import org.springframework.test.context.TestExecutionListeners; @TestExecutionListeners -class TimefoldAutoConfigurationTest { +class TimefoldSolverAutoConfigurationTest { private final ApplicationContextRunner contextRunner; private final ApplicationContextRunner emptyContextRunner; @@ -81,26 +83,32 @@ class TimefoldAutoConfigurationTest { private final FilteredClassLoader testFilteredClassLoader; private final FilteredClassLoader noGizmoFilteredClassLoader; - public TimefoldAutoConfigurationTest() { + public TimefoldSolverAutoConfigurationTest() { contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(NormalSpringTestConfiguration.class); emptyContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(EmptySpringTestConfiguration.class); benchmarkContextRunner = new ApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(TimefoldAutoConfiguration.class, TimefoldBenchmarkAutoConfiguration.class)) + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class, + TimefoldBenchmarkAutoConfiguration.class)) .withUserConfiguration(NormalSpringTestConfiguration.class); gizmoContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(GizmoSpringTestConfiguration.class); chainedContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(ChainedSpringTestConfiguration.class); multimoduleRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(MultiModuleSpringTestConfiguration.class); allDefaultsFilteredClassLoader = new FilteredClassLoader(FilteredClassLoader.PackageFilter.of("ai.timefold.solver.test"), @@ -112,7 +120,8 @@ public TimefoldAutoConfigurationTest() { FilteredClassLoader.ClassPathResourceFilter.of( new ClassPathResource(TimefoldProperties.DEFAULT_SOLVER_CONFIG_URL))); noUserConfigurationContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)); } @Test @@ -467,7 +476,7 @@ void benchmarkWithSpentLimit() { problem.setEntityList(IntStream.range(1, 3) .mapToObj(i -> new TestdataSpringEntity()) .collect(Collectors.toList())); - benchmarkFactory.buildPlannerBenchmark(problem).benchmark(); + assertThat(benchmarkFactory.buildPlannerBenchmark(problem).benchmark()).isNotEmptyDirectory(); }); } @@ -485,7 +494,7 @@ void benchmark() { problem.setEntityList(IntStream.range(1, 3) .mapToObj(i -> new TestdataSpringEntity()) .collect(Collectors.toList())); - benchmarkFactory.buildPlannerBenchmark(problem).benchmark(); + assertThat(benchmarkFactory.buildPlannerBenchmark(problem).benchmark()).isNotEmptyDirectory(); }); } @@ -505,7 +514,7 @@ void benchmarkWithXml() { problem.setEntityList(IntStream.range(1, 3) .mapToObj(i -> new TestdataSpringEntity()) .collect(Collectors.toList())); - benchmarkFactory.buildPlannerBenchmark(problem).benchmark(); + assertThat(benchmarkFactory.buildPlannerBenchmark(problem).benchmark()).isNotEmptyDirectory(); }); } @@ -649,7 +658,7 @@ void multipleEasyScoreConstraints() { .withPropertyValues("timefold.solver.termination.best-score-limit=0") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", DummyChainedSpringEasyScore.class.getSimpleName(), + "Multiple score calculator classes", DummyChainedSpringEasyScore.class.getSimpleName(), DummySpringEasyScore.class.getSimpleName(), "that implements EasyScoreCalculator were found in the classpath."); } @@ -661,7 +670,7 @@ void multipleConstraintProviderConstraints() { .withPropertyValues("timefold.solver.termination.best-score-limit=0") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", TestdataChainedSpringConstraintProvider.class.getSimpleName(), + "Multiple score calculator classes", TestdataChainedSpringConstraintProvider.class.getSimpleName(), TestdataSpringConstraintProvider.class.getSimpleName(), "that implements ConstraintProvider were found in the classpath."); } @@ -673,7 +682,7 @@ void multipleIncrementalScoreConstraints() { .withPropertyValues("timefold.solver.termination.best-score-limit=0") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", DummyChainedSpringIncrementalScore.class.getSimpleName(), + "Multiple score calculator classes", DummyChainedSpringIncrementalScore.class.getSimpleName(), DummySpringIncrementalScore.class.getSimpleName(), "that implements IncrementalScoreCalculator were found in the classpath."); } @@ -686,7 +695,7 @@ void multipleEasyScoreConstraintsXml_property() { "timefold.solver.solver.solver-config-xml=solverConfig.xml") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", + "Multiple score calculator classes", DummyChainedSpringEasyScore.class.getSimpleName(), DummySpringEasyScore.class.getSimpleName(), "that implements EasyScoreCalculator were found in the classpath"); @@ -700,7 +709,7 @@ void multipleConstraintProviderConstraintsXml_property() { "timefold.solver.solver-config-xml=ai/timefold/solver/spring/boot/autoconfigure/normalSolverConfig.xml") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", TestdataChainedSpringConstraintProvider.class.getSimpleName(), + "Multiple score calculator classes", TestdataChainedSpringConstraintProvider.class.getSimpleName(), TestdataSpringConstraintProvider.class.getSimpleName(), "that implements ConstraintProvider were found in the classpath."); } @@ -713,7 +722,7 @@ void multipleIncrementalScoreConstraintsXml_property() { "timefold.solver.solver-config-xml=ai/timefold/solver/spring/boot/autoconfigure/normalSolverConfig.xml") .run(context -> context.getBean("solver1"))) .cause().message().contains( - "Multiple score classes classes", DummyChainedSpringIncrementalScore.class.getSimpleName(), + "Multiple score calculator classes", DummyChainedSpringIncrementalScore.class.getSimpleName(), DummySpringIncrementalScore.class.getSimpleName(), "that implements IncrementalScoreCalculator were found in the classpath."); } diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldMultipleSolverAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java similarity index 96% rename from spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldMultipleSolverAutoConfigurationTest.java rename to spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java index 91dc5e3aa5..60613d7691 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldMultipleSolverAutoConfigurationTest.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java @@ -56,7 +56,7 @@ import org.springframework.test.context.TestExecutionListeners; @TestExecutionListeners -class TimefoldMultipleSolverAutoConfigurationTest { +class TimefoldSolverMultipleSolverAutoConfigurationTest { private final ApplicationContextRunner contextRunner; private final ApplicationContextRunner emptyContextRunner; @@ -67,32 +67,39 @@ class TimefoldMultipleSolverAutoConfigurationTest { private final ApplicationContextRunner multimoduleRunner; private final FilteredClassLoader allDefaultsFilteredClassLoader; - public TimefoldMultipleSolverAutoConfigurationTest() { + public TimefoldSolverMultipleSolverAutoConfigurationTest() { contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(NormalSpringTestConfiguration.class); emptyContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(EmptySpringTestConfiguration.class); benchmarkContextRunner = new ApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(TimefoldAutoConfiguration.class, TimefoldBenchmarkAutoConfiguration.class)) + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class, + TimefoldBenchmarkAutoConfiguration.class)) .withUserConfiguration(NormalSpringTestConfiguration.class); gizmoContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(GizmoSpringTestConfiguration.class); chainedContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(ChainedSpringTestConfiguration.class); multimoduleRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)) .withUserConfiguration(MultiModuleSpringTestConfiguration.class); allDefaultsFilteredClassLoader = new FilteredClassLoader(FilteredClassLoader.PackageFilter.of("ai.timefold.solver.test"), FilteredClassLoader.ClassPathResourceFilter .of(new ClassPathResource(TimefoldProperties.DEFAULT_SOLVER_CONFIG_URL))); noUserConfigurationContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TimefoldAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class)); } @Test @@ -551,7 +558,7 @@ void multipleEasyScoreConstraints() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a EasyScoreCalculator score class, yet there are multiple available", + "don't specify a EasyScoreCalculator score calculator class, yet there are multiple available", DummyChainedSpringEasyScore.class.getSimpleName(), DummySpringEasyScore.class.getSimpleName(), "on the classpath."); @@ -566,7 +573,7 @@ void multipleConstraintProviderConstraints() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a ConstraintProvider score class, yet there are multiple available", + "don't specify a ConstraintProvider score calculator class, yet there are multiple available", TestdataChainedSpringConstraintProvider.class.getSimpleName(), TestdataSpringConstraintProvider.class.getSimpleName(), "on the classpath."); @@ -581,7 +588,7 @@ void multipleIncrementalScoreConstraints() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a IncrementalScoreCalculator score class, yet there are multiple available", + "don't specify a IncrementalScoreCalculator score calculator class, yet there are multiple available", DummyChainedSpringIncrementalScore.class.getSimpleName(), DummySpringIncrementalScore.class.getSimpleName(), "on the classpath."); @@ -598,7 +605,7 @@ void multipleEasyScoreConstraintsXml_property() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a EasyScoreCalculator score class, yet there are multiple available", + "don't specify a EasyScoreCalculator score calculator class, yet there are multiple available", DummyChainedSpringEasyScore.class.getSimpleName(), DummySpringEasyScore.class.getSimpleName(), "on the classpath."); @@ -615,7 +622,7 @@ void multipleConstraintProviderConstraintsXml_property() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a ConstraintProvider score class, yet there are multiple available", + "don't specify a ConstraintProvider score calculator class, yet there are multiple available", TestdataChainedSpringConstraintProvider.class.getSimpleName(), TestdataSpringConstraintProvider.class.getSimpleName(), "on the classpath."); @@ -632,7 +639,7 @@ void multipleIncrementalScoreConstraintsXml_property() { .run(context -> context.getBean("solver1"))) .cause().message().contains( "Some solver configs", "solver2", "solver1", - "don't specify a IncrementalScoreCalculator score class, yet there are multiple available", + "don't specify a IncrementalScoreCalculator score calculator class, yet there are multiple available", DummyChainedSpringIncrementalScore.class.getSimpleName(), DummySpringIncrementalScore.class.getSimpleName(), "on the classpath."); diff --git a/spring-integration/spring-boot-integration-test/pom.xml b/spring-integration/spring-boot-integration-test/pom.xml new file mode 100644 index 0000000000..a44ecdb71a --- /dev/null +++ b/spring-integration/spring-boot-integration-test/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + ai.timefold.solver + timefold-solver-spring-integration + 999-SNAPSHOT + + + spring-boot-integration-test + + + ai.timefold.solver.spring.boot + + **/* + + + + + + org.springframework.boot + spring-boot-dependencies + pom + import + ${version.org.springframework.boot} + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + ai.timefold.solver + timefold-solver-spring-boot-starter + + + org.springframework.boot + spring-boot-autoconfigure + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-webflux + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + true + + + + + + + + native + + + native + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + timefold-spring-boot-integration-test + + paketobuildpacks/builder:tiny + + true + + + + + + process-aot + + process-aot + process-test-aot + + + + + + org.graalvm.buildtools + native-maven-plugin + true + + + build-native + + compile-no-fork + test + + package + + app.native + + + + add-reachability-metadata + + add-reachability-metadata + + + + + app.native + + + -Ob + --no-fallback + + + true + + + + + + + + \ No newline at end of file diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverController.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverController.java new file mode 100644 index 0000000000..95d80ee0d5 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverController.java @@ -0,0 +1,25 @@ +package ai.timefold.solver.spring.boot.it; + +import ai.timefold.solver.core.api.solver.SolverFactory; +import ai.timefold.solver.spring.boot.it.domain.IntegrationTestSolution; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/integration-test") +public class TimefoldSolverController { + private final SolverFactory solverFactory; + + public TimefoldSolverController(SolverFactory solverFactory) { + this.solverFactory = solverFactory; + } + + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public IntegrationTestSolution solve(@RequestBody IntegrationTestSolution problem) { + return solverFactory.buildSolver().solve(problem); + } +} diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverSpringBootApp.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverSpringBootApp.java new file mode 100644 index 0000000000..bc26a7a1eb --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/TimefoldSolverSpringBootApp.java @@ -0,0 +1,12 @@ +package ai.timefold.solver.spring.boot.it; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TimefoldSolverSpringBootApp { + + public static void main(String[] args) { + SpringApplication.run(TimefoldSolverSpringBootApp.class, args); + } +} diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestEntity.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestEntity.java new file mode 100644 index 0000000000..564954e599 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.spring.boot.it.domain; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.lookup.PlanningId; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; + +@PlanningEntity +public class IntegrationTestEntity { + @PlanningId + private String id; + + @PlanningVariable + private IntegrationTestValue value; + + public IntegrationTestEntity() { + } + + public IntegrationTestEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public IntegrationTestValue getValue() { + return value; + } + + public void setValue(IntegrationTestValue value) { + this.value = value; + } +} diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestSolution.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestSolution.java new file mode 100644 index 0000000000..e860d3a1ef --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestSolution.java @@ -0,0 +1,55 @@ +package ai.timefold.solver.spring.boot.it.domain; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.solution.PlanningScore; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; + +@PlanningSolution +public class IntegrationTestSolution { + @PlanningEntityCollectionProperty + private List entityList; + + @ValueRangeProvider + @ProblemFactCollectionProperty + private List valueList; + + @PlanningScore + private SimpleScore score; + + public IntegrationTestSolution() { + } + + public IntegrationTestSolution(List entityList, List valueList) { + this.entityList = entityList; + this.valueList = valueList; + } + + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } +} diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestValue.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestValue.java new file mode 100644 index 0000000000..026e63ab55 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/domain/IntegrationTestValue.java @@ -0,0 +1,4 @@ +package ai.timefold.solver.spring.boot.it.domain; + +public record IntegrationTestValue(String id) { +} diff --git a/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/solver/IntegrationTestConstraintProvider.java b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/solver/IntegrationTestConstraintProvider.java new file mode 100644 index 0000000000..120b7b9445 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/java/ai/timefold/solver/spring/boot/it/solver/IntegrationTestConstraintProvider.java @@ -0,0 +1,19 @@ +package ai.timefold.solver.spring.boot.it.solver; + +import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import ai.timefold.solver.spring.boot.it.domain.IntegrationTestEntity; + +public class IntegrationTestConstraintProvider implements ConstraintProvider { + @Override + public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { + return new Constraint[] { + constraintFactory.forEach(IntegrationTestEntity.class) + .filter(entity -> !entity.getId().equals(entity.getValue().id())) + .penalize(SimpleScore.ONE) + .asConstraint("Entity id do not match value id") + }; + } +} diff --git a/spring-integration/spring-boot-integration-test/src/main/resources/application.properties b/spring-integration/spring-boot-integration-test/src/main/resources/application.properties new file mode 100644 index 0000000000..00ccc41834 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/main/resources/application.properties @@ -0,0 +1 @@ +timefold.solver.termination.best-score-limit=0 \ No newline at end of file diff --git a/spring-integration/spring-boot-integration-test/src/test/java/ai/timefold/solver/spring/boot/it/TimefoldSolverTestResourceIntegrationTest.java b/spring-integration/spring-boot-integration-test/src/test/java/ai/timefold/solver/spring/boot/it/TimefoldSolverTestResourceIntegrationTest.java new file mode 100644 index 0000000000..50460b95f6 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/test/java/ai/timefold/solver/spring/boot/it/TimefoldSolverTestResourceIntegrationTest.java @@ -0,0 +1,72 @@ +package ai.timefold.solver.spring.boot.it; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +import ai.timefold.solver.core.config.solver.SolverConfig; +import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO; +import ai.timefold.solver.spring.boot.it.domain.IntegrationTestEntity; +import ai.timefold.solver.spring.boot.it.domain.IntegrationTestSolution; +import ai.timefold.solver.spring.boot.it.domain.IntegrationTestValue; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.core.io.Resource; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class TimefoldSolverTestResourceIntegrationTest { + + @LocalServerPort + String port; + + @Value("classpath:solver-full.xml") + Resource exampleSolverConfigXml; + + @Test + void testSolve() { + WebTestClient client = WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port + "/integration-test") + .build(); + + IntegrationTestSolution problem = new IntegrationTestSolution( + List.of(new IntegrationTestEntity("0"), + new IntegrationTestEntity("1"), + new IntegrationTestEntity("2")), + List.of(new IntegrationTestValue("0"), + new IntegrationTestValue("1"), + new IntegrationTestValue("2"))); + client.post() + .bodyValue(problem) + .exchange() + .expectBody() + .jsonPath("score").isEqualTo("0") + .jsonPath("entityList").isArray() + .jsonPath("valueList").isArray() + .jsonPath("entityList[0].id").isEqualTo("0") + .jsonPath("entityList[0].value.id").isEqualTo("0") + .jsonPath("entityList[1].id").isEqualTo("1") + .jsonPath("entityList[1].value.id").isEqualTo("1") + .jsonPath("entityList[2].id").isEqualTo("2") + .jsonPath("entityList[2].value.id").isEqualTo("2"); + + } + + @Test + void testSolverXmlParsing() throws IOException { + // Test to verify parsing a complex SolverConfig will work in native image. + // XML file was generated by taking the XSD file available at + // https://timefold.ai/xsd/solver and generating an XML file from it using + // the "Tools > XML Actions > Generate XML Document from XML Schema..." action in IDEA + SolverConfigIO solverConfigIO = new SolverConfigIO(); + SolverConfig solverConfig = solverConfigIO.read(new InputStreamReader(exampleSolverConfigXml.getInputStream())); + assertThat(solverConfig).isNotNull(); + assertThat(solverConfig.getSolutionClass()).isEqualTo(Object.class); + assertThat(solverConfig.getPhaseConfigList()).isNotEmpty(); + } +} diff --git a/spring-integration/spring-boot-integration-test/src/test/resources/META-INF/native-image/resource-config.json b/spring-integration/spring-boot-integration-test/src/test/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000000..d0bd48b897 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/test/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "solver-full.xml" + } + ] + } +} \ No newline at end of file diff --git a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml new file mode 100644 index 0000000000..54d9e0b4d1 --- /dev/null +++ b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml @@ -0,0 +1,1151 @@ + + TRACKED_FULL_ASSERT + false + WELL1024A + 10 + java.lang.Object + 1 + 3 + java.lang.Object + + + MEMORY_USE + + java.lang.Object + + java.lang.Object + GIZMO + + java.lang.Object + + + + + java.lang.Object + + + + + DROOLS + java.lang.Object + + + + + string + + + + java.lang.Object + AND + PT8H + 10 + 10 + 10 + 10 + 10 + string + 10 + 10 + 10 + 10 + 10 + string + string + true + 3 + 3 + 10 + + + + + + + java.lang.Object + AND + PT8H + 10 + 10 + 10 + 10 + 10 + string + 10 + 10 + 10 + 10 + 10 + string + string + false + 3 + 3 + 10 + + + + STRONGEST_FIT + NONE + DECREASING_STRENGTH + + + + java.lang.Object + SOLVER + SHUFFLED + + + + + java.lang.Object + STEP + SHUFFLED + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + STEP + PROBABILISTIC + + java.lang.Object + INCREASING_STRENGTH + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + BETA_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + PHASE + SORTED + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + + + + JUST_IN_TIME + SHUFFLED + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + + java.lang.Object + SOLVER + PROBABILISTIC + + + + + java.lang.Object + SOLVER + SHUFFLED + + java.lang.Object + DECREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + JUST_IN_TIME + SORTED + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + LINEAR_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_DIFFICULTY_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + java.lang.Object + PHASE + SHUFFLED + + + java.lang.Object + PHASE + INHERIT + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + INCREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + PHASE + RANDOM + java.lang.Object + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + 3 + 3 + + java.lang.Object + SOLVER + SORTED + + + java.lang.Object + SOLVER + SHUFFLED + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + java.lang.Object + JUST_IN_TIME + RANDOM + + + java.lang.Object + SOLVER + PROBABILISTIC + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + PARABOLIC_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + STEP + PROBABILISTIC + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + + java.lang.Object + SOLVER + PROBABILISTIC + + + java.lang.Object + JUST_IN_TIME + INHERIT + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + LINEAR_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + INCREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + java.lang.Object + STEP + INHERIT + + + + + java.lang.Object + JUST_IN_TIME + SHUFFLED + + java.lang.Object + DECREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + PHASE + SHUFFLED + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + java.lang.Object + JUST_IN_TIME + SHUFFLED + + + java.lang.Object + STEP + RANDOM + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_STRENGTH + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + java.lang.Object + JUST_IN_TIME + INHERIT + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + java.lang.Object + PHASE + SHUFFLED + + java.lang.Object + DECREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + SOLVER + ORIGINAL + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + + + + JUST_IN_TIME + ORIGINAL + java.lang.Object + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + + java.lang.Object + PHASE + RANDOM + + + java.lang.Object + JUST_IN_TIME + INHERIT + + java.lang.Object + DECREASING_DIFFICULTY_IF_AVAILABLE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + LINEAR_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + java.lang.Object + PHASE + SORTED + + + java.lang.Object + STEP + RANDOM + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BETA_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + INCREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + PHASE + PROBABILISTIC + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + java.lang.Object + + + + + + + STEP + SORTED + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + java.lang.Object + + + + + + + SOLVER + INHERIT + java.lang.Object + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + ALL + java.lang.Object + + + java.lang.Object + PHASE + SHUFFLED + + + + + java.lang.Object + PHASE + RANDOM + + java.lang.Object + INCREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + PHASE + ORIGINAL + + java.lang.Object + INCREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + 3 + 3 + + + java.lang.Object + STEP + SORTED + + + java.lang.Object + PHASE + ORIGINAL + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BETA_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + STEP + PROBABILISTIC + java.lang.Object + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + ALL + java.lang.Object + + + java.lang.Object + PHASE + INHERIT + + + + + java.lang.Object + JUST_IN_TIME + INHERIT + + java.lang.Object + INCREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + JUST_IN_TIME + SHUFFLED + + java.lang.Object + DECREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + LINEAR_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_DIFFICULTY_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + 3 + 3 + + + + java.lang.Object + PHASE + PROBABILISTIC + + + + + java.lang.Object + STEP + INHERIT + + java.lang.Object + DECREASING_STRENGTH + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + 3 + 3 + + + java.lang.Object + SOLVER + RANDOM + + java.lang.Object + INCREASING_STRENGTH_IF_AVAILABLE + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + java.lang.Object + BETA_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + 3 + 3 + + + string + + + + STEP + SHUFFLED + java.lang.Object + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + 1.051732E7 + java.lang.Object + + + java.lang.Object + PHASE + SORTED + + + java.lang.Object + JUST_IN_TIME + PROBABILISTIC + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + LINEAR_DISTRIBUTION + 3 + 3 + 1.051732E7 + 1.051732E7 + + 3 + 3 + 1.051732E7 + 1.051732E7 + + java.lang.Object + NONE + java.lang.Object + java.lang.Object + DESCENDING + java.lang.Object + java.lang.Object + 10 + + 3 + 3 + + + java.lang.Object + PHASE + RANDOM + + + java.lang.Object + STEP + SORTED + + java.lang.Object + DECREASING_DIFFICULTY + java.lang.Object + java.lang.Object + ASCENDING + java.lang.Object + java.lang.Object + 10 + + + + + 3 + 3 + + + java.lang.Object + BLOCK_DISTRIBUTION + 3 + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file