package org.axonframework.eventhandling.tokenstore.jpa; import org.axonframework.common.jpa.ContainerManagedEntityManagerProvider; import org.axonframework.common.jpa.EntityManagerProvider; import org.axonframework.common.transaction.Transaction; import org.axonframework.common.transaction.TransactionManager; import org.axonframework.eventhandling.tokenstore.UnableToClaimTokenException; import org.axonframework.eventsourcing.eventstore.GlobalSequenceTrackingToken; import org.axonframework.eventsourcing.eventstore.TrackingToken; import org.axonframework.serialization.xml.XStreamSerializer; import org.hibernate.dialect.HSQLDialect; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hsqldb.jdbc.JDBCDataSource; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.sql.DataSource; import java.time.Duration; import java.util.Collections; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class JpaTokenStoreTest { @Autowired @Qualifier("jpaTokenStore") private JpaTokenStore jpaTokenStore; @Autowired @Qualifier("concurrentJpaTokenStore") private JpaTokenStore concurrentJpaTokenStore; @Autowired @Qualifier("stealingJpaTokenStore") private JpaTokenStore stealingJpaTokenStore; @PersistenceContext private EntityManager entityManager; @Autowired private PlatformTransactionManager transactionManager; private TransactionTemplate txTemplate; @Before public void setUp() throws Exception { this.txTemplate = new TransactionTemplate(transactionManager); } @Transactional @Test public void testClaimAndUpdateToken() throws Exception { assertNull(jpaTokenStore.fetchToken("test", 0)); jpaTokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); List<TokenEntry> tokens = entityManager.createQuery("SELECT t FROM TokenEntry t " + "WHERE t.processorName = :processorName", TokenEntry.class) .setParameter("processorName", "test") .getResultList(); assertEquals(1, tokens.size()); assertNotNull(tokens.get(0).getOwner()); jpaTokenStore.releaseClaim("test", 0); entityManager.flush(); entityManager.clear(); TokenEntry token = entityManager.find(TokenEntry.class, new TokenEntry.PK("test", 0)); assertNull(token.getOwner()); } @Transactional @Test public void testClaimTokenConcurrently() throws Exception { jpaTokenStore.fetchToken("concurrent", 0); try { concurrentJpaTokenStore.fetchToken("concurrent", 0); fail("Expected UnableToClaimTokenException"); } catch (UnableToClaimTokenException e) { // expected } } @Transactional @Test public void testStealToken() throws Exception { jpaTokenStore.fetchToken("stealing", 0); stealingJpaTokenStore.fetchToken("stealing", 0); try { jpaTokenStore.storeToken(new GlobalSequenceTrackingToken(0), "stealing", 0); fail("Expected UnableToClaimTokenException"); } catch (UnableToClaimTokenException e) { // expected } jpaTokenStore.releaseClaim("stealing", 0); // claim should still be on stealingJpaTokenStore: stealingJpaTokenStore.storeToken(new GlobalSequenceTrackingToken(1), "stealing", 0); } @Test public void testStoreAndLoadAcrossTransactions() { txTemplate.execute(status -> { jpaTokenStore.fetchToken("multi", 0); jpaTokenStore.storeToken(new GlobalSequenceTrackingToken(1), "multi", 0); return null; }); txTemplate.execute(status -> { TrackingToken actual = jpaTokenStore.fetchToken("multi", 0); assertEquals(new GlobalSequenceTrackingToken(1), actual); jpaTokenStore.storeToken(new GlobalSequenceTrackingToken(2), "multi", 0); return null; }); txTemplate.execute(status -> { TrackingToken actual = jpaTokenStore.fetchToken("multi", 0); assertEquals(new GlobalSequenceTrackingToken(2), actual); return null; }); } @Configuration public static class Context { @Bean public EntityManagerProvider entityManagerProvider() { return new ContainerManagedEntityManagerProvider(); } @Bean public DataSource dataSource() { JDBCDataSource dataSource = new JDBCDataSource(); dataSource.setUrl("jdbc:hsqldb:mem:testdb"); dataSource.setUser("sa"); dataSource.setPassword(""); return dataSource; } @Bean public LocalContainerEntityManagerFactoryBean sessionFactory() { LocalContainerEntityManagerFactoryBean sessionFactory = new LocalContainerEntityManagerFactoryBean(); sessionFactory.setPersistenceProvider(new HibernatePersistenceProvider()); sessionFactory.setPackagesToScan(TokenEntry.class.getPackage().getName()); sessionFactory.setJpaPropertyMap(Collections.singletonMap("hibernate.dialect", new HSQLDialect())); sessionFactory.setJpaPropertyMap(Collections.singletonMap("hibernate.hbm2ddl.auto", "create-drop")); sessionFactory.setJpaPropertyMap(Collections.singletonMap("hibernate.connection.url", "jdbc:hsqldb:mem:testdb")); return sessionFactory; } @Bean public PlatformTransactionManager txManager() { return new JpaTransactionManager(); } @Bean public JpaTokenStore jpaTokenStore(EntityManagerProvider entityManagerProvider) { return new JpaTokenStore(entityManagerProvider, new XStreamSerializer()); } @Bean public JpaTokenStore concurrentJpaTokenStore(EntityManagerProvider entityManagerProvider) { return new JpaTokenStore(entityManagerProvider, new XStreamSerializer(), Duration.ofSeconds(2), "concurrent"); } @Bean public JpaTokenStore stealingJpaTokenStore(EntityManagerProvider entityManagerProvider) { return new JpaTokenStore(entityManagerProvider, new XStreamSerializer(), Duration.ofSeconds(-1), "stealing"); } @Bean public TransactionManager transactionManager(PlatformTransactionManager txManager) { return () -> { TransactionStatus transaction = txManager.getTransaction(new DefaultTransactionDefinition()); return new Transaction() { @Override public void commit() { txManager.commit(transaction); } @Override public void rollback() { txManager.rollback(transaction); } }; }; } } }