/** * Perform the actual loading of an aggregate. The necessary locks have been obtained. * * @param aggregateIdentifier the identifier of the aggregate to load * @param expectedVersion The expected version of the aggregate * @return the fully initialized aggregate * @throws AggregateNotFoundException if aggregate with given id cannot be found */ @Override protected LockAwareAggregate<T, A> doLoad(String aggregateIdentifier, Long expectedVersion) { Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { final A aggregate = doLoadWithLock(aggregateIdentifier, expectedVersion); CurrentUnitOfWork.get().onCleanup(u -> lock.release()); return new LockAwareAggregate<>(aggregate, lock); } catch (Throwable ex) { logger.debug("Exception occurred while trying to load an aggregate. Releasing lock.", ex); lock.release(); throw ex; } }
/** * Verifies whether all locks are valid and delegates to * {@link #doDeleteWithLock(Aggregate)} to perform actual deleting. * * @param aggregate the aggregate to delete */ @Override protected final void doDelete(LockAwareAggregate<T, A> aggregate) { if (aggregate.version() != null && !aggregate.isLockHeld()) { throw new ConcurrencyException(String.format( "The aggregate of type [%s] with identifier [%s] could not be " + "saved, as a valid lock is not held. Either another thread has saved an aggregate, or " + "the current thread had released its lock earlier on.", aggregate.getClass().getSimpleName(), aggregate.identifierAsString())); } doDeleteWithLock(aggregate.getWrappedAggregate()); }
@Override protected void prepareForCommit(LockAwareAggregate<T, A> aggregate) { Assert.state(aggregate.isLockHeld(), () -> "An aggregate is being used for which a lock is no longer held"); super.prepareForCommit(aggregate); }
@Test public void testCommandHandlerLoadsSameAggregateTwice() throws Exception { DefaultUnitOfWork.startAndGet(null); repository.newInstance(() -> new StubAggregate(aggregateIdentifier)).execute(StubAggregate::doSomething); CurrentUnitOfWork.commit(); DefaultUnitOfWork.startAndGet(null); repository.load(aggregateIdentifier).execute(StubAggregate::doSomething); repository.load(aggregateIdentifier).execute(StubAggregate::doSomething); CurrentUnitOfWork.commit(); Iterator<? extends DomainEventMessage<?>> es = stubEventStore.readEvents(aggregateIdentifier); assertTrue(es.hasNext()); assertEquals((Object) 0L, es.next().getSequenceNumber()); assertTrue(es.hasNext()); assertEquals((Object) 1L, es.next().getSequenceNumber()); assertTrue(es.hasNext()); assertEquals((Object) 2L, es.next().getSequenceNumber()); assertFalse(es.hasNext()); }
@SuppressWarnings({"unchecked"}) @Test public void testStoreAndLoadNewAggregate() throws Exception { UnitOfWork<?> uow = startAndGetUnitOfWork(); String originalId = repository.newInstance(() -> new JpaAggregate("Hello")).invoke(JpaAggregate::getIdentifier); uow.commit(); entityManager.flush(); entityManager.clear(); List<JpaAggregate> results = entityManager.createQuery("SELECT a FROM JpaAggregate a").getResultList(); assertEquals(1, results.size()); JpaAggregate aggregate = results.get(0); assertEquals(originalId, aggregate.getIdentifier()); uow = startAndGetUnitOfWork(); Aggregate<JpaAggregate> storedAggregate = repository.load(originalId); uow.commit(); assertEquals(storedAggregate.identifierAsString(), originalId); assertTrue(capturedEvents.isEmpty()); }
@Override protected void prepareForCommit(LockAwareAggregate<T, A> aggregate) { Assert.state(aggregate.isLockHeld(), () -> "An aggregate is being used for which a lock is no longer held"); super.prepareForCommit(aggregate); }
/** * Verifies whether all locks are valid and delegates to * {@link #doSaveWithLock(Aggregate)} to perform actual storage. * * @param aggregate the aggregate to store */ @Override protected void doSave(LockAwareAggregate<T, A> aggregate) { if (aggregate.version() != null && !aggregate.isLockHeld()) { throw new ConcurrencyException(String.format( "The aggregate of type [%s] with identifier [%s] could not be " + "saved, as a valid lock is not held. Either another thread has saved an aggregate, or " + "the current thread had released its lock earlier on.", aggregate.getClass().getSimpleName(), aggregate.identifierAsString())); } doSaveWithLock(aggregate.getWrappedAggregate()); }
@Override protected LockAwareAggregate<T, A> doCreateNew(Callable<T> factoryMethod) throws Exception { A aggregate = doCreateNewForLock(factoryMethod); final String aggregateIdentifier = aggregate.identifierAsString(); Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { CurrentUnitOfWork.get().onCleanup(u -> lock.release()); } catch (Throwable ex) { if (lock != null) { logger.debug("Exception occurred while trying to add an aggregate. Releasing lock.", ex); lock.release(); } throw ex; } return new LockAwareAggregate<>(aggregate, lock); }
/** * Verifies whether all locks are valid and delegates to * {@link #doSaveWithLock(Aggregate)} to perform actual storage. * * @param aggregate the aggregate to store */ @Override protected void doSave(LockAwareAggregate<T, A> aggregate) { if (aggregate.version() != null && !aggregate.isLockHeld()) { throw new ConcurrencyException(String.format( "The aggregate of type [%s] with identifier [%s] could not be " + "saved, as a valid lock is not held. Either another thread has saved an aggregate, or " + "the current thread had released its lock earlier on.", aggregate.getClass().getSimpleName(), aggregate.identifierAsString())); } doSaveWithLock(aggregate.getWrappedAggregate()); }
@Override protected LockAwareAggregate<T, A> doCreateNew(Callable<T> factoryMethod) throws Exception { A aggregate = doCreateNewForLock(factoryMethod); final String aggregateIdentifier = aggregate.identifierAsString(); Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { CurrentUnitOfWork.get().onCleanup(u -> lock.release()); } catch (Throwable ex) { if (lock != null) { logger.debug("Exception occurred while trying to add an aggregate. Releasing lock.", ex); lock.release(); } throw ex; } return new LockAwareAggregate<>(aggregate, lock); }
/** * Verifies whether all locks are valid and delegates to * {@link #doDeleteWithLock(Aggregate)} to perform actual deleting. * * @param aggregate the aggregate to delete */ @Override protected final void doDelete(LockAwareAggregate<T, A> aggregate) { if (aggregate.version() != null && !aggregate.isLockHeld()) { throw new ConcurrencyException(String.format( "The aggregate of type [%s] with identifier [%s] could not be " + "saved, as a valid lock is not held. Either another thread has saved an aggregate, or " + "the current thread had released its lock earlier on.", aggregate.getClass().getSimpleName(), aggregate.identifierAsString())); } doDeleteWithLock(aggregate.getWrappedAggregate()); }
/** * Perform the actual loading of an aggregate. The necessary locks have been obtained. * * @param aggregateIdentifier the identifier of the aggregate to load * @param expectedVersion The expected version of the aggregate * @return the fully initialized aggregate * @throws AggregateNotFoundException if aggregate with given id cannot be found */ @Override protected LockAwareAggregate<T, A> doLoad(String aggregateIdentifier, Long expectedVersion) { Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { final A aggregate = doLoadWithLock(aggregateIdentifier, expectedVersion); CurrentUnitOfWork.get().onCleanup(u -> lock.release()); return new LockAwareAggregate<>(aggregate, lock); } catch (Throwable ex) { logger.debug("Exception occurred while trying to load an aggregate. Releasing lock.", ex); lock.release(); throw ex; } }