Last active
May 10, 2023 18:50
-
-
Save edudant/66cd658ba54439c9052681a0d84d66a9 to your computer and use it in GitHub Desktop.
Necessary configuration for Camunda 7.19 and Spring Boot 3.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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