Search code examples
hibernatekotlinquarkusoptaplannerquarkus-panache

Retrieving entities without a CDI request context (Optaplanner, Hibernate and Panache, Kotlin)


I have a configuration entity defined as follows:

@Entity
class ConfigEntity() : PanacheEntity() {

    companion object : PanacheCompanion<ConfigEntity> {
        fun findByKey(key: String): ConfigEntity? {
            return find("stringKey", key).firstResult()
        }
    }

    lateinit var stringKey: String
    lateinit var stringValue: String

}

I would like to be able to let users dynamically set constraints within OptaPlanner, and I have a ConstraintsProvider defined like:

class TimeTableConstraintProvider : ConstraintProvider {

    override fun defineConstraints(constraintFactory: ConstraintFactory): Array<Constraint> {
        val constraints = mutableListOf<Constraint>()


        if (ConfigEntity.findByKey("room")?.stringValue == "ENABLED") constraints.add(roomConflict(constraintFactory))
        if (ConfigEntity.findByKey("teacher")?.stringValue == "ENABLED") constraints.add(teacherConflict(constraintFactory))

        return constraints.toTypedArray()
    }

    //... definitions of roomConflict and teacherConflict below
}

Receiving this error when trying to access entities using Hibernate Panache ORM

(Quarkus Main Thread) Failed to start application (with profile dev): javax.enterprise.context.ContextNotActiveException: Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active. Consider adding @Transactional to your method to automatically activate a transaction, or @ActivateRequestContext if you have valid reasons not to use transactions.

I have tried the recommended solution of adding @Transactional around the defineConstriants function, but it hasn't had any effect.


Solution

  • Dynamically turning constraints off/on like this is error-prone, because ConstraintProvider.defineConstraints() can be called once at bootstrap and cached forever, or can be called in parallel all the time. Giving it a dynamic output like this will cause race conditions, sooner or later.

    To dynamically turn constraints off/on, use penalizeConfigurable() and a @ConstraintConfiguration with @ConstraintWeights of zero/non-zero. Those weights are part of the dataset (the @PlanningSolution), and therefore one dataset might activate constraint A and another dataset might not - and still they can solve in parallel. This approach is thread-safe and doesn't need a CDI request context. See OptaPlanner docs.


    That being said, there's an open RFE to allow defaulting constraints weights on application.properties with no need for a bunch of boilerplate code.