javaquarkusgraalvmgraalvm-native-imagetimefold

Using solverConfig.xml in Timefold for Quarkus causes an error in GraalVM native image


I am working on creating a REST API using Timefold for Quarkus and running it as a native build on Windows-based GraalVM.

I have implemented a POST method named /evaluate, which calculates scores instead of executing jobs. When running it with the mvn quarkus:dev command on the JVM, it works correctly. However, when executing the native build, an error occurs when the POST method is called.

    @Operation(summary = "Calculate and return a score for the allocation schedule.")
    @APIResponses(value = {
            @APIResponse(
                    responseCode = "200",
                    description = "Schedule scores.",
                    content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = StaffScheduleEvaluationResponse.class))
            ),
            @APIResponse(responseCode = "404", description = "Score not found.",
                    content = @Content(mediaType = MediaType.APPLICATION_JSON,
                            schema = @Schema(implementation = ErrorInfo.class))),
            @APIResponse(responseCode = "500", description = "A problem occurred while processing the score.",
                    content = @Content(mediaType = MediaType.APPLICATION_JSON,
                            schema = @Schema(implementation = ErrorInfo.class)))
    })
    @POST
    @Path("/evaluate")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public StaffScheduleEvaluationResponse getEvaluate(StaffSchedule problem) {

        // Score Calculation
        SolverFactory<StaffSchedule> actualSolverFactory = SolverFactory.createFromXmlResource("org/acme/staffscheduling/solverConfig.xml");
        SolutionManager<StaffSchedule, HardMediumSoftScore> solutionManager = SolutionManager.create(actualSolverFactory);
        ScoreAnalysis<HardMediumSoftScore> scoreAnalysis = solutionManager.analyze(problem);
        ScoreAnalysisResult scoreAnalysisResult = generateScoreAnalysis(scoreAnalysis, problem);

        return new StaffScheduleEvaluationResponse(
                scoreAnalysis.score(),
                scoreAnalysisResult
        );
    }

The hierarchy under the resource directory is as follows:

resources
├ org/acme/staffscheduling
│ └ solverConfig.xml
├ META-INF/native-image
│ └ reflect-config.json
└ application.properties

solverConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">

    <!-- Domain model configuration -->
    <solutionClass>org.acme.staffscheduling.domain.StaffSchedule</solutionClass>
    <entityClass>org.acme.staffscheduling.domain.StaffAssignment</entityClass>

    <!-- Score configuration -->
    <scoreDirectorFactory>
        <constraintProviderClass>org.acme.staffscheduling.solver.StaffScheduleConstraintProvider</constraintProviderClass>
    </scoreDirectorFactory>

    <!-- Optimization algorithms configuration -->
    <termination>
        <terminationCompositionStyle>OR</terminationCompositionStyle>
    </termination>

      <constructionHeuristic>
            <constructionHeuristicType>ALLOCATE_ENTITY_FROM_QUEUE</constructionHeuristicType>
            <entitySorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</entitySorterManner>
            <valueSorterManner>DECREASING_STRENGTH_IF_AVAILABLE</valueSorterManner>
      </constructionHeuristic>
    <localSearch>
        <localSearchType>TABU_SEARCH</localSearchType>
        <unionMoveSelector>
            <changeMoveSelector>
                <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
            </changeMoveSelector>
            <swapMoveSelector>
                <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
            </swapMoveSelector>
            <cartesianProductMoveSelector>
                <fixedProbabilityWeight>2.0</fixedProbabilityWeight>
                <changeMoveSelector/>
                <changeMoveSelector/>
                <swapMoveSelector/>
            </cartesianProductMoveSelector>
            <pillarChangeMoveSelector>
                <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
                <subPillarType>ALL</subPillarType>
                <pillarSelector>
                    <minimumSubPillarSize>2</minimumSubPillarSize>
                    <maximumSubPillarSize>4</maximumSubPillarSize>
                </pillarSelector>
            </pillarChangeMoveSelector>
            <pillarSwapMoveSelector>
                <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
                <subPillarType>ALL</subPillarType>
                <pillarSelector>
                    <minimumSubPillarSize>2</minimumSubPillarSize>
                    <maximumSubPillarSize>4</maximumSubPillarSize>
                </pillarSelector>
            </pillarSwapMoveSelector>
        </unionMoveSelector>
    </localSearch>
</solver>

reflect-config.json:

[
  {
    "name": "ai.timefold.solver.core.api.solver.SolverFactory",
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredConstructors": true
  },
  {
    "name": "ai.timefold.solver.core.config.solver.SolverConfig",
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredConstructors": true
  }
]

application.properties:

########################
# Timefold Solver properties
########################
quarkus.timefold.solver.termination.spent-limit=300s

quarkus.arc.context-propagation.enabled=true
quarkus.log.category."ai.timefold.solver".min-level=TRACE
%dev.quarkus.log.category."ai.timefold.solver".level=DEBUG
%prod.quarkus.log.category."ai.timefold.solver".level=INFO
quarkus.log.category."ai.timefold.solver".level=TRACE

# Domain model configuration
# Solution class for the optimization problem
quarkus.timefold.solver.solution-class=org.acme.staffscheduling.domain.StaffSchedule
# Entity class to represent planning variables
quarkus.timefold.solver.entity-classes=org.acme.staffscheduling.domain.StaffAssignment
# Score Director configuration
# Constraint Provider class to define constraints
quarkus.timefold.solver.score-director-factory.constraint-provider-class=org.acme.staffscheduling.solver.StaffScheduleConstraintProvider
quarkus.timefold.solver-config-xml=org/acme/staffscheduling/solverConfig.xml
# Termination conditions
# Multiple termination conditions are combined with OR logic
quarkus.timefold.solver.termination.composition-style=OR
# Maximum time without improvement to terminate solving
quarkus.timefold.solver.termination.unimproved-spent-limit=60s

########################
# Native build properties
########################
# Enable Swagger UI also in the native mode
quarkus.swagger-ui.always-include=true
quarkus.native.resources.includes=solver.xsd,org/acme/staffscheduling/solverConfig.xml

########################
# CORS Configuration
########################
# Enable CORS
quarkus.http.cors=true
# Allow all origins (modify as needed for security)
quarkus.http.cors.origins=*
# Allow specific HTTP methods
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
# Allow specific headers in requests
quarkus.http.cors.headers=Authorization,Content-Type
# Expose specific headers to clients
quarkus.http.cors.exposed-headers=Authorization
## Cache preflight requests for 24 hours
#quarkus.http.cors.access-control-max-age=24H


########################
# logging file
########################
quarkus.log.file.enable=true
quarkus.log.file.path=logs/application.log
quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.file.level=TRACE
quarkus.log.file.rotation.max-file-size=10M
quarkus.log.file.rotation.max-backup-index=5

error log:

__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
・・・
2024-12-27 17:23:52,271 INFO  [ai.tim.sol.qua.bea.TimefoldSolverBannerBean] (main) Using Timefold Solver Community Edition v1.16.0.
2024-12-27 17:23:52,273 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.numHeapArenas: 16
2024-12-27 17:23:52,273 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.numDirectArenas: 16
2024-12-27 17:23:52,273 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.pageSize: 8192
2024-12-27 17:23:52,273 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.maxOrder: 3
2024-12-27 17:23:52,273 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.chunkSize: 65536
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.smallCacheSize: 256
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.normalCacheSize: 64
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.maxCachedBufferCapacity: 32768
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.cacheTrimInterval: 8192
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.cacheTrimIntervalMillis: 0
2024-12-27 17:23:52,274 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.useCacheForAllThreads: false
2024-12-27 17:23:52,275 DEBUG [io.net.buf.PooledByteBufAllocator] (vert.x-eventloop-thread-0) -Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023
2024-12-27 17:23:52,275 DEBUG [io.net.buf.ByteBufUtil] (vert.x-eventloop-thread-0) -Dio.netty.allocator.type: pooled
2024-12-27 17:23:52,275 DEBUG [io.net.buf.ByteBufUtil] (vert.x-eventloop-thread-0) -Dio.netty.threadLocalDirectBufferSize: 0
2024-12-27 17:23:52,275 DEBUG [io.net.buf.ByteBufUtil] (vert.x-eventloop-thread-0) -Dio.netty.maxThreadLocalCharBufferSize: 16384
2024-12-27 17:23:52,275 DEBUG [io.net.boo.ChannelInitializerExtensions] (vert.x-eventloop-thread-0) -Dio.netty.bootstrap.extensions: null
2024-12-27 17:23:52,276 INFO  [io.quarkus] (main) staff-scheduling 1.0.0-SNAPSHOT native (powered by Quarkus 3.16.3) started in 0.159s. Listening on: http://0.0.0.0:8080
2024-12-27 17:23:52,277 INFO  [io.quarkus] (main) Profile prod activated.
2024-12-27 17:23:52,277 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-openapi, swagger-ui, timefold-solver, timefold-solver-jackson, vertx]
2024-12-27 17:24:21,337 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) RESTEASY002315: PathInfo: /schedules/evaluate
2024-12-27 17:24:21,340 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) Interceptor Context: org.jboss.resteasy.core.interception.jaxrs.ServerReaderInterceptorContext,  Method : proceed
2024-12-27 17:24:21,340 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) MessageBodyReader: org.jboss.resteasy.core.providerfactory.SortedKey
2024-12-27 17:24:21,341 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) MessageBodyReader: org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider
2024-12-27 17:24:21,341 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) Provider : org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider,  Method : readFrom
2024-12-27 17:24:21,404 FINE  [jak.xml.bind] (executor-thread-1) Checking system property jakarta.xml.bind.JAXBContextFactory
2024-12-27 17:24:21,404 FINE  [jak.xml.bind] (executor-thread-1)   not found
2024-12-27 17:24:21,405 FINE  [jak.xml.bind] (executor-thread-1) Trying to create the platform default provider
2024-12-27 17:24:21,405 FINE  [jak.xml.bind] (executor-thread-1) Unable to find from OSGi: [jakarta.xml.bind.JAXBContextFactory]: java.lang.ClassNotFoundException: org.glassfish.hk2.osgiresourcelocator.ServiceLoader. This exception was synthesized during native image building from a call to java.lang.Class.forName(String) with constant arguments.
        at jakarta.xml.bind.ServiceLoaderUtil.lookupUsingOSGiServiceLoader(ServiceLoaderUtil.java:57)
        at jakarta.xml.bind.ContextFinder.find(ContextFinder.java:378)
        at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:605)
        at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:546)
        at ai.timefold.solver.core.impl.io.jaxb.GenericJaxbIO.<init>(GenericJaxbIO.java:76)
        at ai.timefold.solver.core.impl.io.jaxb.GenericJaxbIO.<init>(GenericJaxbIO.java:68)
        at ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO.<init>(SolverConfigIO.java:12)
        at ai.timefold.solver.core.config.solver.SolverConfig.createFromXmlReader(SolverConfig.java:194)
        at ai.timefold.solver.core.config.solver.SolverConfig.createFromXmlInputStream(SolverConfig.java:171)
        at ai.timefold.solver.core.config.solver.SolverConfig.createFromXmlResource(SolverConfig.java:117)
        at ai.timefold.solver.core.config.solver.SolverConfig.createFromXmlResource(SolverConfig.java:92)
        at ai.timefold.solver.core.api.solver.SolverFactory.createFromXmlResource(SolverFactory.java:42)
        at org.acme.staffscheduling.rest.StaffScheduleResource.getEvaluate(StaffScheduleResource.java:647)
        at java.base@21.0.5/java.lang.reflect.Method.invoke(Method.java:580)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:356)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invokePropagateNotFound$6(SynchronousDispatcher.java:275)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
        at org.jboss.resteasy.core.SynchronousDispatcher.invokePropagateNotFound(SynchronousDispatcher.java:260)
        at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:86)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:97)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:627)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
        at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base@21.0.5/java.lang.Thread.runWith(Thread.java:1596)
        at java.base@21.0.5/java.lang.Thread.run(Thread.java:1583)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:896)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:872)

2024-12-27 17:24:21,412 FINE  [jak.xml.bind] (executor-thread-1) Trying to create the platform default provider
2024-12-27 17:24:21,412 FINE  [jak.xml.bind] (executor-thread-1) loaded org.glassfish.jaxb.runtime.v2.ContextFactory from null
2024-12-27 17:24:21,412 FINE  [jak.xml.bind] (executor-thread-1) Using jakarta.xml.bind-api on the class path.
2024-12-27 17:24:21,413 FINE  [org.gla.jax.run.v2.ContextFactory] (executor-thread-1) Property org.glassfish.jaxb.XmlAccessorFactoryis not active.  Using JAXB's implementation
2024-12-27 17:24:21,431 DEBUG [io.ver.ext.web.RoutingContext] (executor-thread-1) RoutingContext failure (500): org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalArgumentException: The scoreDirectorFactory lacks configuration for either constraintProviderClass, easyScoreCalculatorClass or incrementalScoreCalculatorClass.
        at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:107)
        at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:344)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:205)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:452)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invokePropagateNotFound$6(SynchronousDispatcher.java:275)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
        at org.jboss.resteasy.core.SynchronousDispatcher.invokePropagateNotFound(SynchronousDispatcher.java:260)
        at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:86)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:97)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:627)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
        at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base@21.0.5/java.lang.Thread.runWith(Thread.java:1596)
        at java.base@21.0.5/java.lang.Thread.run(Thread.java:1583)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:896)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:872)
Caused by: java.lang.IllegalArgumentException: The scoreDirectorFactory lacks configuration for either constraintProviderClass, easyScoreCalculatorClass or incrementalScoreCalculatorClass.
        at ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory.decideMultipleScoreDirectorFactories(ScoreDirectorFactoryFactory.java:78)
        at ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory.buildScoreDirectorFactory(ScoreDirectorFactoryFactory.java:26)
        at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.buildScoreDirectorFactory(DefaultSolverFactory.java:191)
        at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:70)
        at ai.timefold.solver.core.api.solver.SolverFactory.createFromXmlResource(SolverFactory.java:43)
        at org.acme.staffscheduling.rest.StaffScheduleResource.getEvaluate(StaffScheduleResource.java:647)
        at java.base@21.0.5/java.lang.reflect.Method.invoke(Method.java:580)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:356)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
        ... 20 more

2024-12-27 17:24:21,437 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /schedules/evaluate failed, error id: a5a547d3-9aa1-4236-aa88-eeb66ef227f1-1: org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalArgumentException: The scoreDirectorFactory lacks configuration for either constraintProviderClass, easyScoreCalculatorClass or incrementalScoreCalculatorClass.
        at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:107)
        at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:344)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:205)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:452)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invokePropagateNotFound$6(SynchronousDispatcher.java:275)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
        at org.jboss.resteasy.core.SynchronousDispatcher.invokePropagateNotFound(SynchronousDispatcher.java:260)
        at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:86)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:97)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:627)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
        at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base@21.0.5/java.lang.Thread.runWith(Thread.java:1596)
        at java.base@21.0.5/java.lang.Thread.run(Thread.java:1583)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:896)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:872)
Caused by: java.lang.IllegalArgumentException: The scoreDirectorFactory lacks configuration for either constraintProviderClass, easyScoreCalculatorClass or incrementalScoreCalculatorClass.
        at ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory.decideMultipleScoreDirectorFactories(ScoreDirectorFactoryFactory.java:78)
        at ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory.buildScoreDirectorFactory(ScoreDirectorFactoryFactory.java:26)
        at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.buildScoreDirectorFactory(DefaultSolverFactory.java:191)
        at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:70)
        at ai.timefold.solver.core.api.solver.SolverFactory.createFromXmlResource(SolverFactory.java:43)
        at org.acme.staffscheduling.rest.StaffScheduleResource.getEvaluate(StaffScheduleResource.java:647)
        at java.base@21.0.5/java.lang.reflect.Method.invoke(Method.java:580)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:356)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
        ... 20 more

If anyone could help me resolve this issue, I would greatly appreciate it.

After the revision:

By following the advice you kindly provided, I was able to resolve the issue with the following adjustments.

  1. I removed the reflect-config.json file because it wasn't needed.
  2. I deleted the following section from solverConfig.xml and application.properties.

quarkus.timefold.solver.score-director-factory.constraint-provider-class (in application.properties)

<scoreDirectorFactory>, <solutionClass> and <entityClass> (in solverConfig.xml)

  1. Using the Timefold Quarkus extension, I updated it to use the proxy of @Inject for SolverManager, SolutionManager, or SolverFactory. (in StaffScheduleResource.java)
    SolverManager<StaffSchedule, String> solverManager;
    SolutionManager<StaffSchedule, HardMediumSoftScore> solutionManager;
    SolverFactory<StaffSchedule> solverFactory;

    @Inject
    public StaffScheduleResource(SolverManager<StaffSchedule, String> solverManager, SolutionManager<StaffSchedule, HardMediumSoftScore> solutionManager, SolverFactory<StaffSchedule> solverFactory) {
        this.solverManager = solverManager;
        this.solutionManager = solutionManager;
        this.solverFactory = solverFactory;
    }
  1. Inject SolutionManager as a CDI bean After the change:
    /** Attempt 1: To use the CDI proxy of solverFactory, the following line was removed: */
    // SolverFactory<StaffSchedule> actualSolverFactory = SolverFactory.createFromXmlResource("org/acme/staffscheduling/solverConfig.xml");
    // SolutionManager<StaffSchedule, HardMediumSoftScore> solutionManager = SolutionManager.create(actualSolverFactory);
    /** Attempt 2: A ClassCastException occurred when attempting to use the CDI proxy of solverFactory with SolutionManager.create. This line turned out to be unnecessary.*/
    // SolutionManager<StaffSchedule, HardMediumSoftScore> solutionManager = SolutionManager.create(solverFactory);
    ScoreAnalysis<HardMediumSoftScore> scoreAnalysis = solutionManager.analyze(problem);

Solution

  • at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.(DefaultSolverFactory.java:70) at ai.timefold.solver.core.api.solver.SolverFactory.createFromXmlResource(SolverFactory.java:43) at org.acme.staffscheduling.rest.StaffScheduleResource.getEvaluate(StaffScheduleResource.java:647)

    That SolverFactory.createX call is weird. In quarkus, you should just @Inject SolverManager, SolutionManager or SolverFactory, if the timefold-solver-quarkus dependency is in your pom.xml. Let the timefold quarkus extension figure out how to create those.

    I suspect that StaffScheduleResource.java line 647 isn't even using org/acme/staffscheduling/solverConfig.xml! Otherwise the error message makes no sense at all.