Skip to content

Instantly share code, notes, and snippets.

@edudant
Last active May 10, 2023 18:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edudant/66cd658ba54439c9052681a0d84d66a9 to your computer and use it in GitHub Desktop.
Save edudant/66cd658ba54439c9052681a0d84d66a9 to your computer and use it in GitHub Desktop.
Necessary configuration for Camunda 7.19 and Spring Boot 3.
package cz.datalite.tsm.process.camunda.boot3
import jakarta.persistence.EntityManager
import jakarta.persistence.EntityManagerFactory
import jakarta.persistence.PersistenceException
import jakarta.persistence.TransactionRequiredException
import org.camunda.bpm.engine.ProcessEngine
import org.camunda.bpm.engine.ProcessEngineException
import org.camunda.bpm.engine.impl.cfg.*
import org.camunda.bpm.engine.impl.context.Context
import org.camunda.bpm.engine.impl.interceptor.CommandContext
import org.camunda.bpm.engine.impl.interceptor.Session
import org.camunda.bpm.engine.impl.interceptor.SessionFactory
import org.camunda.bpm.engine.spring.ProcessEngineFactoryBean
import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration
import org.camunda.bpm.engine.spring.SpringProcessEngineServicesConfiguration
import org.camunda.bpm.spring.boot.starter.*
import org.camunda.bpm.spring.boot.starter.configuration.CamundaDatasourceConfiguration
import org.camunda.bpm.spring.boot.starter.configuration.CamundaJpaConfiguration
import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration
import org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultMetricsConfiguration
import org.camunda.bpm.spring.boot.starter.event.ProcessApplicationEventPublisher
import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties
import org.camunda.bpm.spring.boot.starter.property.ManagementProperties
import org.camunda.bpm.spring.boot.starter.util.CamundaSpringBootUtil.initCustomFields
import org.springframework.boot.autoconfigure.AutoConfigureAfter
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.annotation.*
import org.springframework.orm.jpa.EntityManagerFactoryUtils
/**
* Necessary configuration for Camunda 7.19 and Spring Boot 3.
*
* Replace the Original Camunda Autoconfiguration class.
* The only difference in code is SpringBoot3CamundaConfiguration import instead of original CamundaBpmConfiguration.
*
* Resolved problems:
* 1) Spring Boot 3 no longer supports autoconfiguration via META-INF/spring.factories, you need to use
* META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports instead.
* We put this class on ClassPath scanned by @SpringBootApplication, hence it is automaticaly imported even without Autoconfiguration.
* 2) Change of javax.persistence to jakarta.persistence in various places.
*
* @author Jiri Bubnik
*/
@EnableConfigurationProperties(CamundaBpmProperties::class, ManagementProperties::class)
@Import(
SpringBoot3CamundaConfiguration::class,
CamundaBpmActuatorConfiguration::class,
CamundaBpmPluginConfiguration::class,
CamundaBpmTelemetryConfiguration::class,
SpringProcessEngineServicesConfiguration::class
)
@Configuration
@AutoConfigureAfter(HibernateJpaAutoConfiguration::class)
class SpringBoot3CamundaAutoConfiguration {
@Bean
fun processEngineFactoryBean(processEngineConfigurationImpl: ProcessEngineConfigurationImpl,
camundaDatasourceConfiguration: CamundaDatasourceConfiguration
): ProcessEngineFactoryBean? {
val factoryBean = ProcessEngineFactoryBean()
factoryBean.processEngineConfiguration = processEngineConfigurationImpl
return factoryBean
}
@Bean
@Primary
fun commandExecutorTxRequired(processEngineConfigurationImpl: ProcessEngineConfigurationImpl) =
processEngineConfigurationImpl.commandExecutorTxRequired
@Bean
fun commandExecutorTxRequiresNew(processEngineConfigurationImpl: ProcessEngineConfigurationImpl) =
processEngineConfigurationImpl.commandExecutorTxRequiresNew
@Bean
fun commandExecutorSchemaOperations(processEngineConfigurationImpl: ProcessEngineConfigurationImpl) =
processEngineConfigurationImpl.commandExecutorSchemaOperations
@Bean
fun processApplicationEventPublisher(publisher: ApplicationEventPublisher?) = ProcessApplicationEventPublisher(publisher)
}
/**
* Replace several beans from original configuration with Spring Boot 3 compatible versions.
* The main problem to resolve is javax.persistence package change to jakarta.persistence.
*/
class SpringBoot3CamundaConfiguration: CamundaBpmConfiguration() {
@Bean
override fun processEngineConfigurationImpl(processEnginePlugins: List<ProcessEnginePlugin>): ProcessEngineConfigurationImpl {
val configuration = initCustomFields(SpringBoot3ProcessInstanceConfiguration())
configuration.processEnginePlugins.add(CompositeProcessEnginePlugin(processEnginePlugins))
return configuration
}
@Bean
fun camundaJpaConfiguration(jpaEntityManagerFactory: EntityManagerFactory): CamundaJpaConfiguration =
object: AbstractCamundaConfiguration(), CamundaJpaConfiguration {
override fun preInit(configuration: ProcessEngineConfigurationImpl) {
val jpa = camundaBpmProperties.jpa
configuration.jpaPersistenceUnitName = jpa.persistenceUnitName
configuration.jpaEntityManagerFactory = jpaEntityManagerFactory
configuration.isJpaCloseEntityManager = jpa.isCloseEntityManager
configuration.isJpaHandleTransaction = jpa.isHandleTransaction
}
override fun postInit(processEngineConfiguration: ProcessEngineConfigurationImpl) {}
override fun postProcessEngineBuild(processEngine: ProcessEngine) {}
}
/**
* Camunda FIX
* Spring @PostInit init() is called after preInit() producing null pointer exception on metrics object
*/
@Bean
fun camundaMetricsConfiguration() = object: DefaultMetricsConfiguration() {
override fun preInit(configuration: SpringProcessEngineConfiguration) {
val metrics = camundaBpmProperties.metrics
configuration.isMetricsEnabled = metrics.isEnabled
configuration.isDbMetricsReporterActivate = metrics.isDbReporterActivate
}
}
}
/**
* Just replace javax.persistence package with jakarta.persistence.
*/
class SpringBoot3ProcessInstanceConfiguration: SpringProcessEngineConfiguration() {
init {
setApplicationContext(applicationContext)
}
override fun initJpa() {
if (jpaPersistenceUnitName != null) {
// NOT SUPPORTED jpaEntityManagerFactory = JpaHelper.createEntityManagerFactory(jpaPersistenceUnitName)
}
if (jpaEntityManagerFactory != null) {
sessionFactories[SpringBoot3EntityManagerSession::class.java] =
SpringBoot3SpringEntityManagerSessionFactory(
jpaEntityManagerFactory,
jpaHandleTransaction,
jpaCloseEntityManager
)
// NOT SUPPORTED yet
// val jpaType =
// variableSerializers.getSerializerByName(JPAVariableSerializer.NAME) as JPAVariableSerializer
// // Add JPA-type
// if (jpaType == null) {
// // We try adding the variable right after byte serializer, if available
// val serializableIndex = variableSerializers.getSerializerIndexByName(ValueType.BYTES.name)
// if (serializableIndex > -1) {
// variableSerializers.addSerializer(JPAVariableSerializer(), serializableIndex)
// } else {
// variableSerializers.addSerializer(JPAVariableSerializer())
// }
// }
}
sessionFactories[SpringBoot3EntityManagerSession::class.java] = SpringBoot3SpringEntityManagerSessionFactory(
jpaEntityManagerFactory,
jpaHandleTransaction,
jpaCloseEntityManager
)
}
}
/**
* Just replace javax.persistence package with jakarta.persistence.
*/
open class SpringBoot3SpringEntityManagerSessionFactory(
val entityManagerFactory: Any,
val handleTransactions: Boolean,
val closeEntityManager: Boolean
) : SessionFactory {
override fun getSessionType() = EntityManagerFactory::class.java
override fun openSession(): Session {
val typedEntityManagerFactory = entityManagerFactory as EntityManagerFactory
val entityManager = EntityManagerFactoryUtils.getTransactionalEntityManager(typedEntityManagerFactory)
?: return SpringBoot3EntityManagerSessionImpl(typedEntityManagerFactory, null, handleTransactions, closeEntityManager)
return SpringBoot3EntityManagerSessionImpl(typedEntityManagerFactory, entityManager, false, false)
}
}
/**
* Just replace javax.persistence package with jakarta.persistence.
*/
class SpringBoot3EntityManagerSessionImpl(
private val entityManagerFactory: EntityManagerFactory,
private var entityManager: EntityManager?,
private val handleTransactions: Boolean,
private val closeEntityManager: Boolean
) : SpringBoot3EntityManagerSession {
override fun flush() {
if (entityManager != null && (!handleTransactions || isTransactionActive)) {
try {
entityManager!!.flush()
} catch (ise: IllegalStateException) {
throw ProcessEngineException("Error while flushing EntityManager, illegal state", ise)
} catch (tre: TransactionRequiredException) {
throw ProcessEngineException("Cannot flush EntityManager, an active transaction is required", tre)
} catch (pe: PersistenceException) {
throw ProcessEngineException("Error while flushing EntityManager: " + pe.message, pe)
}
}
}
val isTransactionActive: Boolean
get() = if (handleTransactions && entityManager?.transaction != null) {
entityManager!!.transaction.isActive
} else false
override fun close() {
if (closeEntityManager && entityManager != null && !entityManager!!.isOpen) {
try {
entityManager!!.close()
} catch (ise: IllegalStateException) {
throw ProcessEngineException(
"Error while closing EntityManager, may have already been closed or it is container-managed",
ise
)
}
}
}
override fun getEntityManager(): EntityManager {
if (entityManager == null) {
entityManager = entityManagerFactory.createEntityManager()
if (handleTransactions) {
// Add transaction listeners, if transactions should be handled
val jpaTransactionCommitListener: TransactionListener = object : TransactionListener {
override fun execute(commandContext: CommandContext) {
if (isTransactionActive) {
entityManager!!.getTransaction().commit()
}
}
}
val jpaTransactionRollbackListener: TransactionListener = object : TransactionListener {
override fun execute(commandContext: CommandContext) {
if (isTransactionActive) {
entityManager!!.getTransaction().rollback()
}
}
}
val transactionContext = Context.getCommandContext().transactionContext
transactionContext.addTransactionListener(TransactionState.COMMITTED, jpaTransactionCommitListener)
transactionContext.addTransactionListener(TransactionState.ROLLED_BACK, jpaTransactionRollbackListener)
// Also, start a transaction, if one isn't started already
if (!isTransactionActive) {
entityManager!!.getTransaction().begin()
}
}
}
return entityManager!!
}
}
/**
* Just replace javax.persistence package with jakarta.persistence.
*/
interface SpringBoot3EntityManagerSession : Session {
/**
* Get an [EntityManager] instance associated with this session.
* @throws ProcessEngineException when no [EntityManagerFactory] instance
* is configured for the process engine.
*/
fun getEntityManager(): EntityManager
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment