diff --git a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicAccessioningService.java b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicAccessioningService.java index f4d52b0b..068333f0 100644 --- a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicAccessioningService.java +++ b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicAccessioningService.java @@ -177,6 +177,10 @@ public void merge(ACCESSION accessionOrigin, ACCESSION mergeInto, String reason) dbService.merge(accessionOrigin, mergeInto, reason); } + public void shutDownAccessioning() { + accessionGenerator.shutDownAccessionGenerator(); + } + protected AccessionGenerator getAccessionGenerator() { return accessionGenerator; } diff --git a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/exceptions/AccessionGeneratorShutDownException.java b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/exceptions/AccessionGeneratorShutDownException.java new file mode 100644 index 00000000..70834011 --- /dev/null +++ b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/core/exceptions/AccessionGeneratorShutDownException.java @@ -0,0 +1,7 @@ +package uk.ac.ebi.ampt2d.commons.accession.core.exceptions; + +public class AccessionGeneratorShutDownException extends RuntimeException { + public AccessionGeneratorShutDownException(String msg) { + super(msg); + } +} diff --git a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/AccessionGenerator.java b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/AccessionGenerator.java index e99acfab..9bfb9bec 100644 --- a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/AccessionGenerator.java +++ b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/AccessionGenerator.java @@ -50,4 +50,9 @@ List> generateAccessions(Map response); + + /** + * This method should be used to shut-down the generator and release resources + */ + void shutDownAccessionGenerator(); } diff --git a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/SingleAccessionGenerator.java b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/SingleAccessionGenerator.java index 1d4d3669..2a389715 100644 --- a/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/SingleAccessionGenerator.java +++ b/accession-commons-core/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/SingleAccessionGenerator.java @@ -67,4 +67,8 @@ public static SingleAccessionGenerator ofSHA1AccessionGen return ofHashAccessionGenerator(summaryFunction, new SHA1HashingFunction()); } + @Override + public void shutDownAccessionGenerator() { + // Do nothing - no resources to release + } } diff --git a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManager.java b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManager.java index 6735a4c5..04b95e85 100644 --- a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManager.java +++ b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManager.java @@ -168,4 +168,14 @@ public Set recoverState(long[] committedElements) throws Acce this.availableRanges.addAll(newAvailableRanges); return doCommit(committedElements); } + + public List getAssignedBlocks(){ + return assignedBlocks.stream().collect(Collectors.toList()); + } + + public void shutDownBlockManager() { + assignedBlocks.clear(); + availableRanges.clear(); + generatedAccessions.clear(); + } } diff --git a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGenerator.java b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGenerator.java index 9abefd59..6bdac838 100644 --- a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGenerator.java +++ b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGenerator.java @@ -19,6 +19,7 @@ import uk.ac.ebi.ampt2d.commons.accession.block.initialization.BlockInitializationException; import uk.ac.ebi.ampt2d.commons.accession.core.exceptions.AccessionCouldNotBeGeneratedException; +import uk.ac.ebi.ampt2d.commons.accession.core.exceptions.AccessionGeneratorShutDownException; import uk.ac.ebi.ampt2d.commons.accession.core.exceptions.AccessionIsNotPendingException; import uk.ac.ebi.ampt2d.commons.accession.core.models.AccessionWrapper; import uk.ac.ebi.ampt2d.commons.accession.core.models.SaveResponse; @@ -47,6 +48,8 @@ public class MonotonicAccessionGenerator implements AccessionGenerator uncompletedBlocks = blockService - .getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, - applicationInstanceId); + .reserveUncompletedBlocksForCategoryIdAndApplicationInstanceId(categoryId, applicationInstanceId); //Insert as available ranges for (ContiguousIdBlock block : uncompletedBlocks) { blockManager.addBlock(block); @@ -116,6 +118,7 @@ private void recoverState(long[] committedElements) throws AccessionIsNotPending public synchronized long[] generateAccessions(int numAccessionsToGenerate) throws AccessionCouldNotBeGeneratedException { + checkAccessionGeneratorNotShutDown(); long[] accessions = new long[numAccessionsToGenerate]; reserveNewBlocksUntilSizeIs(numAccessionsToGenerate); @@ -147,20 +150,24 @@ private synchronized void reserveNewBlock(String categoryId, String instanceId) } public synchronized void commit(long... accessions) throws AccessionIsNotPendingException { + checkAccessionGeneratorNotShutDown(); blockService.save(blockManager.commit(accessions)); } public synchronized void release(long... accessions) throws AccessionIsNotPendingException { + checkAccessionGeneratorNotShutDown(); blockManager.release(accessions); } public synchronized MonotonicRangePriorityQueue getAvailableRanges() { + checkAccessionGeneratorNotShutDown(); return blockManager.getAvailableRanges(); } @Override public List> generateAccessions(Map messages) throws AccessionCouldNotBeGeneratedException { + checkAccessionGeneratorNotShutDown(); long[] accessions = generateAccessions(messages.size()); int i = 0; List> accessionedModels = new ArrayList<>(); @@ -174,8 +181,27 @@ public List> generateAccessions(Map response) { + checkAccessionGeneratorNotShutDown(); commit(response.getSavedAccessions().stream().mapToLong(l -> l).toArray()); release(response.getSaveFailedAccessions().stream().mapToLong(l -> l).toArray()); } + public void shutDownAccessionGenerator(){ + List blockList = blockManager.getAssignedBlocks(); + blockList.stream().forEach(block -> block.releaseReserved()); + blockService.save(blockList); + blockManager.shutDownBlockManager(); + SHUTDOWN = true; + } + + /** + * Before doing any operation on Accession Generator, we need to make sure it has not been shut down. + * We should make the check by calling this method as the first thing in all public methods of this class + */ + private void checkAccessionGeneratorNotShutDown(){ + if(SHUTDOWN){ + throw new AccessionGeneratorShutDownException("Accession Generator has been shut down and is no longer available"); + } + } + } diff --git a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/entities/ContiguousIdBlock.java b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/entities/ContiguousIdBlock.java index dc40c1b3..12ae9409 100644 --- a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/entities/ContiguousIdBlock.java +++ b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/entities/ContiguousIdBlock.java @@ -25,6 +25,7 @@ import javax.persistence.Index; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; /** * This class represents a block allocated by an application instance, in a monotonic sequence associated with a @@ -63,6 +64,10 @@ public class ContiguousIdBlock implements Comparable { private long lastCommitted; + private boolean reserved; + + private LocalDateTime createdTimestamp; + // Create / update dates ContiguousIdBlock() { @@ -75,6 +80,8 @@ public ContiguousIdBlock(String categoryId, String applicationInstanceId, long f this.firstValue = firstValue; this.lastValue = firstValue + size - 1; this.lastCommitted = firstValue - 1; + this.reserved = true; + this.createdTimestamp = LocalDateTime.now(); } /** @@ -127,6 +134,10 @@ public long getId() { return id; } + public String getCategoryId() { + return categoryId; + } + public long getLastCommitted() { return lastCommitted; } @@ -135,6 +146,14 @@ public void setLastCommitted(long lastCommitted) { this.lastCommitted = lastCommitted; } + public String getApplicationInstanceId() { + return applicationInstanceId; + } + + public void setApplicationInstanceId(String applicationInstanceId) { + this.applicationInstanceId = applicationInstanceId; + } + public long getFirstValue() { return firstValue; } @@ -143,6 +162,26 @@ public long getLastValue() { return lastValue; } + public boolean isReserved() { + return reserved == true; + } + + public boolean isNotReserved() { + return reserved == false; + } + + public void markAsReserved() { + this.reserved = true; + } + + public void releaseReserved() { + this.reserved = false; + } + + public boolean isFull() { + return lastCommitted == lastValue; + } + public boolean isNotFull() { return lastCommitted != lastValue; } diff --git a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/repositories/ContiguousIdBlockRepository.java b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/repositories/ContiguousIdBlockRepository.java index c5628d32..b21b15bc 100644 --- a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/repositories/ContiguousIdBlockRepository.java +++ b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/repositories/ContiguousIdBlockRepository.java @@ -17,20 +17,18 @@ */ package uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.repositories; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.entities.ContiguousIdBlock; -import java.util.stream.Stream; +import java.util.List; @Repository public interface ContiguousIdBlockRepository extends CrudRepository { - - ContiguousIdBlock findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(String categoryId, - String instanceId); - - Stream findAllByCategoryIdAndApplicationInstanceIdOrderByLastValueAsc(String categoryId, - String instanceId); + @Query("SELECT cib FROM ContiguousIdBlock cib WHERE cib.categoryId = :categoryId AND cib.lastCommitted != cib.lastValue AND (cib.reserved IS NULL OR cib.reserved IS FALSE) ORDER BY cib.lastValue asc") + List findUncompletedAndUnreservedBlocksOrderByLastValueAsc(@Param("categoryId") String categoryId); ContiguousIdBlock findFirstByCategoryIdOrderByLastValueDesc(String categoryId); } diff --git a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockService.java b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockService.java index 2dc38869..2b36104a 100644 --- a/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockService.java +++ b/accession-commons-monotonic-generator-jpa/src/main/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockService.java @@ -27,9 +27,32 @@ import javax.persistence.PersistenceContext; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; +/** + * The ContiguousIdBlockService is used by AccessionGenerator to enter/update block information in DB. + * + * In case of multiprocessing, we need to make sure a block is used by only one AccessionGenerator at any point of time. + * To prevent a block from being used by multiple AccessionGenerator, we mark the block as reserved (using column reserved) + * when they are in use by an AccessionGenerator. A block, marked as reserved implies it is currently being used by an + * AccessionGenerator and should not be picked up for use by any other AccessionGenerator. + * + * Whenever an AccessionGenerator asks for a block from the ContiguousIdBlockService for using the accessions in it, we + * should reserve the block for the calling Accession Generator. + * + * Existing Uncompleted Blocks + * When an AccessionGenerator starts, it asks for Uncompleted Blocks, in order to use the remaining accessions + * in them. As these blocks, will be used by the calling AccessionGenerator, we need to explicitly mark them + * as reserved in DB. + * (see method @reserveUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc) + * New Block + * When an AccessionGenerator asks for a new block, we create a new block with correct values (based on the given + * parameters and existing blocks) and save it in DB. A newly created block is implicitly marked as reserved. + * (see method @reserveNewBlock) + * + * Also, when saving the blocks, we need to check for the block's last committed value. + * If it's last committed value is same as last value, we should release the block in DB + * + */ public class ContiguousIdBlockService { private ContiguousIdBlockRepository repository; @@ -47,6 +70,8 @@ public ContiguousIdBlockService(ContiguousIdBlockRepository repository, Map blocks) { + // release block if full + blocks.forEach(block -> {if (block.isFull()) {block.releaseReserved();}}); repository.saveAll(blocks); entityManager.flush(); } @@ -74,12 +99,15 @@ public BlockParameters getBlockParameters(String categoryId) { return categoryBlockInitializations.get(categoryId); } - @Transactional(readOnly = true) - public List getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc( - String categoryId, String applicationInstanceId) { - try (Stream reservedBlocksOfThisInstance = repository - .findAllByCategoryIdAndApplicationInstanceIdOrderByLastValueAsc(categoryId, applicationInstanceId)) { - return reservedBlocksOfThisInstance.filter(ContiguousIdBlock::isNotFull).collect(Collectors.toList()); - } + @Transactional(isolation = Isolation.SERIALIZABLE) + public List reserveUncompletedBlocksForCategoryIdAndApplicationInstanceId(String categoryId, String applicationInstanceId) { + List blockList = repository.findUncompletedAndUnreservedBlocksOrderByLastValueAsc(categoryId); + blockList.stream().forEach(block -> { + block.setApplicationInstanceId(applicationInstanceId); + block.markAsReserved(); + }); + save(blockList); + return blockList; } + } diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicMonotonicAccessioningWithAlternateRangesTest.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicMonotonicAccessioningWithAlternateRangesTest.java index 232aa2d8..b8f876d1 100644 --- a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicMonotonicAccessioningWithAlternateRangesTest.java +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/core/BasicMonotonicAccessioningWithAlternateRangesTest.java @@ -28,10 +28,10 @@ import uk.ac.ebi.ampt2d.commons.accession.core.models.AccessionWrapper; import uk.ac.ebi.ampt2d.commons.accession.core.models.GetOrCreateAccessionWrapper; import uk.ac.ebi.ampt2d.commons.accession.generators.monotonic.MonotonicAccessionGenerator; -import uk.ac.ebi.ampt2d.commons.accession.generators.monotonic.MonotonicRange; import uk.ac.ebi.ampt2d.commons.accession.generators.monotonic.MonotonicRangePriorityQueue; import uk.ac.ebi.ampt2d.commons.accession.hashing.SHA1HashingFunction; import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.entities.ContiguousIdBlock; +import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.repositories.ContiguousIdBlockRepository; import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.service.ContiguousIdBlockService; import uk.ac.ebi.ampt2d.commons.accession.service.BasicSpringDataRepositoryMonotonicDatabaseService; import uk.ac.ebi.ampt2d.test.configuration.TestMonotonicDatabaseServiceTestConfiguration; @@ -46,6 +46,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getAllBlocksForCategoryId; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getAllUncompletedBlocksForCategoryId; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getUnreservedContiguousIdBlock; @RunWith(SpringRunner.class) @DataJpaTest @@ -60,6 +63,9 @@ public class BasicMonotonicAccessioningWithAlternateRangesTest { @Autowired private ContiguousIdBlockService contiguousIdBlockService; + @Autowired + private ContiguousIdBlockRepository contiguousIdBlockRepository; + @Test(expected = BlockInitializationException.class) public void testUnknownCategory() throws AccessionCouldNotBeGeneratedException { List> evaAccessions = @@ -75,12 +81,12 @@ public void testRecoverState() { // create 3 un-complete contiguous id blocks of size 10 // block-1 : (100 to 109), block-2 : (110 to 119), block-3 : (120 to 129) List uncompletedBlocks = new ArrayList<>(); - uncompletedBlocks.add(new ContiguousIdBlock(categoryId, instanceId2, 100, 10)); - uncompletedBlocks.add(new ContiguousIdBlock(categoryId, instanceId2, 110, 10)); - uncompletedBlocks.add(new ContiguousIdBlock(categoryId, instanceId2, 120, 10)); + uncompletedBlocks.add(getUnreservedContiguousIdBlock(categoryId, instanceId2, 100, 10)); + uncompletedBlocks.add(getUnreservedContiguousIdBlock(categoryId, instanceId2, 110, 10)); + uncompletedBlocks.add(getUnreservedContiguousIdBlock(categoryId, instanceId2, 120, 10)); contiguousIdBlockService.save(uncompletedBlocks); - assertEquals(3, contiguousIdBlockService.getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, instanceId2).size()); + assertEquals(3, getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).size()); // create and save accessions in db (100 to 124) - save 2 sets of same accessions with different hashes List> accessionsSet1 = LongStream.range(100l, 125l) @@ -101,8 +107,8 @@ public void testRecoverState() { // block-1 (100 to 109) : fully complete // block-2 (110 to 119) : fully complete // block-3 (120 to 124) : partially complete - assertEquals(1, contiguousIdBlockService.getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, instanceId2).size()); - ContiguousIdBlock uncompletedBlock = contiguousIdBlockService.getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, instanceId2).get(0); + assertEquals(1, getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).size()); + ContiguousIdBlock uncompletedBlock = getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).get(0); assertEquals(120l, uncompletedBlock.getFirstValue()); assertEquals(129l, uncompletedBlock.getLastValue()); assertEquals(124l, uncompletedBlock.getLastCommitted()); @@ -117,22 +123,21 @@ public void testRecoverState() { public void testAlternateRangesWithDifferentGenerators() throws AccessionCouldNotBeGeneratedException { /* blockStartValue= 0, blockSize= 10 , nextBlockInterval= 20 the new blocks are interleaved or jumped for each 20 items accessioned - so the accesions will be in the range of 0-19,40-59,80-99 */ + so the accessions will be in the range of 0-19,40-59,80-99 */ String categoryId = "eva_2"; String instanceId2 = "test-instance_2"; - List> evaAccessions = getAccessioningService(categoryId, INSTANCE_ID) - .getOrCreate(getObjectsForAccessionsInRange(1, 9)); + BasicAccessioningService accService1 = getAccessioningService(categoryId, INSTANCE_ID); + List> evaAccessions = accService1.getOrCreate(getObjectsForAccessionsInRange(1, 9)); assertEquals(9, evaAccessions.size()); assertEquals(0, evaAccessions.get(0).getAccession().longValue()); assertEquals(8, evaAccessions.get(8).getAccession().longValue()); //BlockSize of 10 was reserved but only 9 elements have been accessioned - assertEquals(1, contiguousIdBlockService - .getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, INSTANCE_ID) - .size()); + assertEquals(1, getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).size()); + accService1.shutDownAccessioning(); //Get another service for same category - evaAccessions = getAccessioningService(categoryId, INSTANCE_ID) - .getOrCreate(getObjectsForAccessionsInRange(11, 30)); + BasicAccessioningService accService2 = getAccessioningService(categoryId, INSTANCE_ID); + evaAccessions = accService2.getOrCreate(getObjectsForAccessionsInRange(11, 30)); assertEquals(20, evaAccessions.size()); //Previous block ended here as only 9 elements were accessioned out of a blocksize of 10 assertEquals(9, evaAccessions.get(0).getAccession().longValue()); @@ -145,27 +150,65 @@ public void testAlternateRangesWithDifferentGenerators() throws AccessionCouldNo assertEquals(40, evaAccessions.get(11).getAccession().longValue()); assertEquals(48, evaAccessions.get(19).getAccession().longValue()); //BlockSize if 10 was reserved but only 9 elements have been accessioned - assertEquals(1, contiguousIdBlockService.getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc - (categoryId, INSTANCE_ID).size()); + assertEquals(1, getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).size()); + accService2.shutDownAccessioning(); //Get another service for same category but different Instance - evaAccessions = getAccessioningService(categoryId, instanceId2) - .getOrCreate(getObjectsForAccessionsInRange(31, 39)); + BasicAccessioningService accService3 = getAccessioningService(categoryId, instanceId2); + evaAccessions = accService3.getOrCreate(getObjectsForAccessionsInRange(31, 39)); assertEquals(9, evaAccessions.size()); //New Block from different instance have not jumped as still blocks are available before interleaving point assertNotEquals(80, evaAccessions.get(0).getAccession().longValue()); - assertEquals(50, evaAccessions.get(0).getAccession().longValue()); - assertEquals(58, evaAccessions.get(8).getAccession().longValue()); - assertEquals(1, contiguousIdBlockService - .getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(categoryId, instanceId2).size()); + assertEquals(49, evaAccessions.get(0).getAccession().longValue()); + assertEquals(57, evaAccessions.get(8).getAccession().longValue()); + assertEquals(1, getAllUncompletedBlocksForCategoryId(contiguousIdBlockRepository, categoryId).size()); + accService3.shutDownAccessioning(); //Get previous uncompleted service from instance1 and create accessions - evaAccessions = getAccessioningService(categoryId, INSTANCE_ID) - .getOrCreate(getObjectsForAccessionsInRange(40, 41)); - assertEquals(2, evaAccessions.size()); - assertEquals(49, evaAccessions.get(0).getAccession().longValue()); //Block ended here + BasicAccessioningService accService4 = getAccessioningService(categoryId, INSTANCE_ID); + evaAccessions = accService4.getOrCreate(getObjectsForAccessionsInRange(40, 42)); + assertEquals(3, evaAccessions.size()); + assertEquals(58, evaAccessions.get(0).getAccession().longValue()); //Block ended here //New Block with 20 interval from last block made in instanceId2 - assertEquals(80, evaAccessions.get(1).getAccession().longValue()); + assertEquals(80, evaAccessions.get(2).getAccession().longValue()); + } + + @Test + public void testInitializeBlockManagerInMonotonicAccessionGenerator() { + String categoryId = "eva_2"; + String instanceId2 = "test-instance_2"; + + ContiguousIdBlock block = getUnreservedContiguousIdBlock(categoryId, instanceId2, 0, 10); + contiguousIdBlockRepository.save(block); + + // assert block is not full and not reserved + List blockInDBList = getAllBlocksForCategoryId(contiguousIdBlockRepository, categoryId); + assertEquals(1, blockInDBList.size()); + List unreservedAndNotFullBlocks = blockInDBList.stream() + .filter(b -> b.isNotFull() && b.isNotReserved()) + .collect(Collectors.toList()); + assertEquals(1, unreservedAndNotFullBlocks.size()); + assertEquals(9, unreservedAndNotFullBlocks.get(0).getLastValue()); + assertEquals(-1, unreservedAndNotFullBlocks.get(0).getLastCommitted()); + assertEquals(false, unreservedAndNotFullBlocks.get(0).isReserved()); + + // this will run the recover state + BasicAccessioningService accService = getAccessioningService(categoryId, instanceId2); + + // assert block gets reserved after recover state + blockInDBList = getAllBlocksForCategoryId(contiguousIdBlockRepository, categoryId); + assertEquals(1, blockInDBList.size()); + unreservedAndNotFullBlocks = blockInDBList.stream() + .filter(b -> b.isNotFull() && b.isNotReserved()) + .collect(Collectors.toList()); + assertEquals(0, unreservedAndNotFullBlocks.size()); + List reservedAndNotFullBlocks = blockInDBList.stream() + .filter(b -> b.isNotFull() && b.isReserved()) + .collect(Collectors.toList()); + assertEquals(1, reservedAndNotFullBlocks.size()); + assertEquals(9, reservedAndNotFullBlocks.get(0).getLastValue()); + assertEquals(-1, reservedAndNotFullBlocks.get(0).getLastCommitted()); + assertEquals(true, reservedAndNotFullBlocks.get(0).isReserved()); } private List getObjectsForAccessionsInRange(int startRange, int endRange) { @@ -173,7 +216,7 @@ private List getObjectsForAccessionsInRange(int startRange, int endRa .toList()); } - private AccessioningService getAccessioningService(String categoryId, + private BasicAccessioningService getAccessioningService(String categoryId, String instanceId) { return new BasicAccessioningService<>( getGenerator(categoryId, instanceId), diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManagerTest.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManagerTest.java index 0ba9a4dd..29b4952a 100644 --- a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManagerTest.java +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/BlockManagerTest.java @@ -153,4 +153,25 @@ public void commitAllValuesOnBlockManager() throws AccessionCouldNotBeGeneratedE manager.commit(accessions2); } + @Test + public void testGetAssignedBlocks(){ + BlockManager manager = new BlockManager(); + manager.addBlock(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 0, 10)); + manager.addBlock(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 10, 10)); + + assertEquals(2, manager.getAssignedBlocks().size()); + } + + @Test + public void testShutDownBlockManager(){ + BlockManager manager = new BlockManager(); + manager.addBlock(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 0, 10)); + manager.addBlock(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 10, 10)); + manager.shutDownBlockManager(); + + assertEquals(0, manager.getAssignedBlocks().size()); + assertEquals(0, manager.getAvailableRanges().size()); + + } + } diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGeneratorTest.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGeneratorTest.java index ee2d68b4..b0f2738f 100644 --- a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGeneratorTest.java +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/generators/monotonic/MonotonicAccessionGeneratorTest.java @@ -26,6 +26,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; +import uk.ac.ebi.ampt2d.commons.accession.core.exceptions.AccessionGeneratorShutDownException; import uk.ac.ebi.ampt2d.commons.accession.core.exceptions.AccessionIsNotPendingException; import uk.ac.ebi.ampt2d.commons.accession.core.models.AccessionWrapper; import uk.ac.ebi.ampt2d.commons.accession.core.models.SaveResponse; @@ -35,11 +36,13 @@ import uk.ac.ebi.ampt2d.commons.accession.utils.exceptions.ExponentialBackOffMaxRetriesRuntimeException; import uk.ac.ebi.ampt2d.test.configuration.MonotonicAccessionGeneratorTestConfiguration; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; @@ -49,6 +52,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getAllBlocksForCategoryId; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getUnreservedContiguousIdBlock; @RunWith(SpringRunner.class) @DataJpaTest @@ -109,14 +114,14 @@ public void assertNewBlockGeneratedInSecondInstance() throws Exception { generator1.generateAccessions(TENTH_BLOCK_SIZE); assertEquals(1, repository.count()); - block = repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(0, block.getFirstValue()); assertEquals(BLOCK_SIZE - 1, block.getLastValue()); assertEquals(-1, block.getLastCommitted()); generator2.generateAccessions(TENTH_BLOCK_SIZE); assertEquals(2, repository.count()); - block = repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_2_ID); + block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(BLOCK_SIZE, block.getFirstValue()); assertEquals(2 * BLOCK_SIZE - 1, block.getLastValue()); assertEquals(BLOCK_SIZE - 1, block.getLastCommitted()); @@ -159,8 +164,7 @@ public void assertCommitModifiesLastCommitted() throws Exception { generator.commit(accessions); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(TENTH_BLOCK_SIZE - 1, block.getLastCommitted()); } @@ -169,8 +173,7 @@ public void assertNotCommittingDoesNotModifyLastCommitted() throws Exception { MonotonicAccessionGenerator generator = getMonotonicAccessionGenerator(); long[] accessions = generator.generateAccessions(TENTH_BLOCK_SIZE); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(-1, block.getLastCommitted()); } @@ -182,13 +185,12 @@ public void assertCommitOutOfOrderDoesNotModifyLastCommittedUntilTheSequenceIsCo generator.commit(accessions2); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(-1, block.getLastCommitted()); generator.commit(accessions1); - block = repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(2 * TENTH_BLOCK_SIZE - 1, block.getLastCommitted()); } @@ -201,14 +203,13 @@ public void assertCommitOutOfOrderDoesNotModifyLastCommittedUntilTheSequenceIsCo generator.commit(accessions2); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(BLOCK_SIZE, block.getFirstValue()); assertEquals(BLOCK_SIZE - 1, block.getLastCommitted()); generator.commit(accessions1); - block = repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(BLOCK_SIZE, block.getFirstValue()); assertEquals(BLOCK_SIZE + 2 * TENTH_BLOCK_SIZE - 1, block.getLastCommitted()); } @@ -271,8 +272,7 @@ public void assertMultipleReleaseAndCommitsWorks() throws Exception { generator.release(8, 9, 10); generator.commit(getLongArray(12, 998)); //999 is waiting somewhere taking a big nap and no elements have been confirmed due to element 0 being released - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(-1, block.getLastCommitted()); long[] accessions2 = generator.generateAccessions(BLOCK_SIZE); @@ -294,7 +294,7 @@ public void assertMultipleReleaseAndCommitsWorks() throws Exception { assertEquals(998, blockResult.get().getLastCommitted()); // 999 is committed and then the remaining elements get confirmed generator.commit(999); - block = repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(1991, block.getLastCommitted()); } @@ -302,12 +302,12 @@ public void assertMultipleReleaseAndCommitsWorks() throws Exception { public void assertRecoverNoPendingCommit() throws Exception { MonotonicAccessionGenerator generator = getMonotonicAccessionGenerator(); long[] accessions1 = generator.generateAccessions(BLOCK_SIZE); + generator.shutDownAccessionGenerator(); // Now assume that the db layer has stored some elements and that the application has died and restarted. MonotonicAccessionGenerator generatorRecovering = new MonotonicAccessionGenerator(CATEGORY_ID, INSTANCE_ID, service, new long[]{2, 3, 5}); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(-1, block.getLastCommitted()); assertFalse(generatorRecovering.getAvailableRanges().isEmpty()); assertThat(generatorRecovering.getAvailableRanges(), @@ -319,12 +319,12 @@ public void assertRecoverPendingCommit() throws Exception { MonotonicAccessionGenerator generator = getMonotonicAccessionGenerator(); long[] accessions1 = generator.generateAccessions(BLOCK_SIZE); generator.commit(0, 1); + generator.shutDownAccessionGenerator(); // Now assume that the db layer has stored some elements and that the application has died and restarted. MonotonicAccessionGenerator generatorRecovering = new MonotonicAccessionGenerator( CATEGORY_ID, INSTANCE_ID, service, new long[]{2, 3, 5}); - ContiguousIdBlock block = - repository.findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID, INSTANCE_ID); + ContiguousIdBlock block = findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(CATEGORY_ID); assertEquals(3, block.getLastCommitted()); assertThat(generatorRecovering.getAvailableRanges(), contains(new MonotonicRange(4, 4), new MonotonicRange(6, BLOCK_SIZE - 1))); @@ -420,6 +420,7 @@ public void assertReleaseInAlternateRanges() throws Exception { public void assertRecoverInAlternateRanges() throws Exception { MonotonicAccessionGenerator generator = getMonotonicAccessionGeneratorForCategoryHavingBlockInterval(); long[] accessions1 = generator.generateAccessions(NUM_OF_ACCESSIONS); + generator.shutDownAccessionGenerator(); // Now assume that the db layer has stored some elements and that the application has died and restarted. MonotonicAccessionGenerator generatorRecovering = new MonotonicAccessionGenerator(CATEGORY_ID_2, INSTANCE_ID, service, new long[]{2, 3}); @@ -451,4 +452,75 @@ public void assertAbortExecutionWhenDBConstraintExceptionThrown() { assertThrows(ExponentialBackOffMaxRetriesRuntimeException.class, () -> mockGenerator.generateAccessions(1)); assertEquals(0, repository.count()); } + + @Test + public void testInitializeBlockManager() { + ContiguousIdBlock block = getUnreservedContiguousIdBlock(CATEGORY_ID_2, INSTANCE_ID, 0, 10); + repository.save(block); + + // To start with Block is UnCompleted and UnReserved + List blockInDBList = findAllByCategoryIdAndApplicationInstanceIdOrderByLastValueAsc(CATEGORY_ID_2); + assertEquals(1, blockInDBList.size()); + List unreservedBlocks = blockInDBList.stream() + .filter(b -> b.isNotFull() && b.isNotReserved()) + .collect(Collectors.toList()); + assertEquals(1, unreservedBlocks.size()); + assertEquals(0, unreservedBlocks.get(0).getFirstValue()); + assertEquals(9, unreservedBlocks.get(0).getLastValue()); + assertEquals(-1, unreservedBlocks.get(0).getLastCommitted()); + assertEquals(false, unreservedBlocks.get(0).isReserved()); + + // Generator 1 starts and its recover state reserves the UnCompleted block + MonotonicAccessionGenerator generator1 = new MonotonicAccessionGenerator(CATEGORY_ID_2, INSTANCE_ID, service, new long[]{}); + assertEquals(1, generator1.getAvailableRanges().size()); + assertEquals(new MonotonicRange(0, 9), generator1.getAvailableRanges().peek()); + + // Block is currently reserved by Generator-1 + blockInDBList = findAllByCategoryIdAndApplicationInstanceIdOrderByLastValueAsc(CATEGORY_ID_2); + assertEquals(1, blockInDBList.size()); + List reservedBlocks = blockInDBList.stream() + .filter(b -> b.isNotFull() && b.isReserved()) + .collect(Collectors.toList()); + assertEquals(1, reservedBlocks.size()); + assertEquals(0, reservedBlocks.get(0).getFirstValue()); + assertEquals(9, reservedBlocks.get(0).getLastValue()); + assertEquals(-1, reservedBlocks.get(0).getLastCommitted()); + assertEquals(true, reservedBlocks.get(0).isReserved()); + + // Generator-2 will not be able to reserve the un-completed block as it is currently reserved by Generator-1 + MonotonicAccessionGenerator generator2 = new MonotonicAccessionGenerator(CATEGORY_ID_2, INSTANCE_ID, service, new long[]{}); + assertEquals(0, generator2.getAvailableRanges().size()); + + // Generator-3 can reserve the same Uncompleted block, once Generator-1 releases it + generator1.shutDownAccessionGenerator(); + MonotonicAccessionGenerator generator3 = new MonotonicAccessionGenerator(CATEGORY_ID_2, INSTANCE_ID, service, new long[]{}); + assertEquals(1, generator3.getAvailableRanges().size()); + assertEquals(new MonotonicRange(0, 9), generator3.getAvailableRanges().peek()); + } + + @Test + public void testShutDownAccessionGenerator() { + MonotonicAccessionGenerator generator = getMonotonicAccessionGeneratorForCategoryHavingBlockInterval(); + generator.shutDownAccessionGenerator(); + + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.generateAccessions(24)); + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.generateAccessions(new HashMap())); + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.commit()); + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.release()); + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.postSave(new SaveResponse<>())); + assertThrows(AccessionGeneratorShutDownException.class, () -> generator.getAvailableRanges()); + } + + private List findAllByCategoryIdAndApplicationInstanceIdOrderByLastValueAsc(String categoryId) { + return getAllBlocksForCategoryId(repository, categoryId).stream() + .sorted(Comparator.comparing(ContiguousIdBlock::getLastValue)) + .collect(Collectors.toList()); + } + + private ContiguousIdBlock findFirstByCategoryIdAndApplicationInstanceIdOrderByLastValueDesc(String categoryId) { + return getAllBlocksForCategoryId(repository, categoryId).stream() + .sorted(Comparator.comparing(ContiguousIdBlock::getLastValue).reversed()) + .findFirst().get(); + } + } diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockServiceTest.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockServiceTest.java index 47959ed6..9872eab4 100644 --- a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockServiceTest.java +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/persistence/jpa/monotonic/service/ContiguousIdBlockServiceTest.java @@ -37,6 +37,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getAllBlocksForCategoryId; +import static uk.ac.ebi.ampt2d.commons.accession.util.ContiguousIdBlockUtil.getUnreservedContiguousIdBlock; @RunWith(SpringRunner.class) @DataJpaTest @@ -96,24 +98,40 @@ public void testReserveNewBlocksWithMultipleInstances() { } @Test - public void testGetUncompleteBlocks() { - ContiguousIdBlock uncompletedBlock = new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 0, 5); - ContiguousIdBlock completedBlock = new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 10, 5); - completedBlock.setLastCommitted(14); + public void testGetUncompletedBlocks() { + // unreserved and uncompleted + ContiguousIdBlock uncompletedBlock = getUnreservedContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 0, 5); + // unreserved and uncompleted with different instance id + ContiguousIdBlock uncompletedAndUnreservedWithDifferentInstanceId = getUnreservedContiguousIdBlock(CATEGORY_ID, INSTANCE_ID_2, 5, 5); + + // unreserved but completed + ContiguousIdBlock unreservedButCompletedBlock = getUnreservedContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 10, 5); + unreservedButCompletedBlock.setLastCommitted(14); + // uncompleted but reserved + ContiguousIdBlock UncompletedButReservedBlock = new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 15, 5); + // completed and reserved + ContiguousIdBlock completedAndReservedBlock = new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 20, 5); + completedAndReservedBlock.setLastCommitted(24); service.save(Arrays.asList(uncompletedBlock)); - service.save(Arrays.asList(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID_2, 5, 5))); - service.save(Arrays.asList(completedBlock)); - service.save(Arrays.asList(new ContiguousIdBlock(CATEGORY_ID, INSTANCE_ID, 15, 5))); + service.save(Arrays.asList(uncompletedAndUnreservedWithDifferentInstanceId)); + service.save(Arrays.asList(unreservedButCompletedBlock)); + service.save(Arrays.asList(UncompletedButReservedBlock)); + service.save(Arrays.asList(completedAndReservedBlock)); - List contiguousBlocks = - service.getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(CATEGORY_ID, INSTANCE_ID); + // Reserve Uncompleted blocks - should only reserve uncompleted and unreserved + String reservingInstanceId = "instance-Id-3"; + List contiguousBlocks = service.reserveUncompletedBlocksForCategoryIdAndApplicationInstanceId(CATEGORY_ID, reservingInstanceId); assertEquals(2, contiguousBlocks.size()); assertEquals(0, contiguousBlocks.get(0).getFirstValue()); assertEquals(4, contiguousBlocks.get(0).getLastValue()); - assertEquals(15, contiguousBlocks.get(1).getFirstValue()); - assertEquals(19, contiguousBlocks.get(1).getLastValue()); + assertEquals(reservingInstanceId, contiguousBlocks.get(0).getApplicationInstanceId()); + assertTrue(contiguousBlocks.get(0).isReserved()); + assertEquals(5, contiguousBlocks.get(1).getFirstValue()); + assertEquals(9, contiguousBlocks.get(1).getLastValue()); + assertEquals(reservingInstanceId, contiguousBlocks.get(1).getApplicationInstanceId()); + assertTrue(contiguousBlocks.get(1).isReserved()); } @Test @@ -125,11 +143,16 @@ public void testBlockSizeAndIntervalForCategory() { assertEquals(2000, block2.getFirstValue()); assertEquals(2999, block2.getLastValue()); - List contiguousBlocks = service - .getUncompletedBlocksByCategoryIdAndApplicationInstanceIdOrderByEndAsc(CATEGORY_ID_2, INSTANCE_ID); + List contiguousBlocks = getAllBlocksForCategoryId(repository, CATEGORY_ID_2); assertEquals(2, contiguousBlocks.size()); assertTrue(contiguousBlocks.get(0).isNotFull()); + assertEquals(0, contiguousBlocks.get(0).getFirstValue()); + assertEquals(-1, contiguousBlocks.get(0).getLastCommitted()); + assertEquals(999, contiguousBlocks.get(0).getLastValue()); assertTrue(contiguousBlocks.get(1).isNotFull()); + assertEquals(2000, contiguousBlocks.get(1).getFirstValue()); + assertEquals(1999, contiguousBlocks.get(1).getLastCommitted()); + assertEquals(2999, contiguousBlocks.get(1).getLastValue()); } @Test diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/util/ContiguousIdBlockUtil.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/util/ContiguousIdBlockUtil.java new file mode 100644 index 00000000..2af82088 --- /dev/null +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/commons/accession/util/ContiguousIdBlockUtil.java @@ -0,0 +1,93 @@ +package uk.ac.ebi.ampt2d.commons.accession.util; + +import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.entities.ContiguousIdBlock; +import uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.repositories.ContiguousIdBlockRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class ContiguousIdBlockUtil { + + public static ContiguousIdBlock getUnreservedContiguousIdBlock(ContiguousIdBlock block) { + block.releaseReserved(); + return block; + } + + public static ContiguousIdBlock getUnreservedContiguousIdBlock(String categoryId, String instanceId, long firstValue, long size) { + ContiguousIdBlock block = new ContiguousIdBlock(categoryId, instanceId, firstValue, size); + block.releaseReserved(); + return block; + } + + public static List getAllBlocksInDB(ContiguousIdBlockRepository repository) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllBlocksForCategoryId(ContiguousIdBlockRepository repository, String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllUncompletedAndUnReservedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isNotFull()) + .filter(block -> block.isNotReserved()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllUncompletedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isNotFull()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllCompletedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isFull()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllUnreservedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isNotReserved()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllReservedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isReserved()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + + public static List getAllCompletedAndReservedBlocksForCategoryId(ContiguousIdBlockRepository repository, + String categoryId) { + return StreamSupport.stream(repository.findAll().spliterator(), false) + .filter(block -> block.getCategoryId().equals(categoryId)) + .filter(block -> block.isFull()) + .filter(block -> block.isReserved()) + .sorted(Comparator.comparing(ContiguousIdBlock::getFirstValue)) + .collect(Collectors.toList()); + } + +} diff --git a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/test/configuration/TestMonotonicDatabaseServiceTestConfiguration.java b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/test/configuration/TestMonotonicDatabaseServiceTestConfiguration.java index 2ad15983..ea76d134 100644 --- a/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/test/configuration/TestMonotonicDatabaseServiceTestConfiguration.java +++ b/accession-commons-monotonic-generator-jpa/src/test/java/uk/ac/ebi/ampt2d/test/configuration/TestMonotonicDatabaseServiceTestConfiguration.java @@ -39,7 +39,7 @@ @Configuration @EnableSpringDataContiguousIdService @EntityScan("uk.ac.ebi.ampt2d.test.persistence") -@EnableJpaRepositories(basePackages = "uk.ac.ebi.ampt2d.test.persistence") +@EnableJpaRepositories(basePackages = {"uk.ac.ebi.ampt2d.test.persistence", "uk.ac.ebi.ampt2d.commons.accession.persistence.jpa.monotonic.repositories"}) public class TestMonotonicDatabaseServiceTestConfiguration { private static final String CATEGORY_ID = "category-id-monotonic-test"; diff --git a/accession-commons-test/src/main/java/uk/ac/ebi/ampt2d/test/rest/MockTestAccessionGenerator.java b/accession-commons-test/src/main/java/uk/ac/ebi/ampt2d/test/rest/MockTestAccessionGenerator.java index b2bd5c0b..bc7e18de 100644 --- a/accession-commons-test/src/main/java/uk/ac/ebi/ampt2d/test/rest/MockTestAccessionGenerator.java +++ b/accession-commons-test/src/main/java/uk/ac/ebi/ampt2d/test/rest/MockTestAccessionGenerator.java @@ -72,4 +72,8 @@ public void postSave(SaveResponse response) { // Do nothing } + @Override + public void shutDownAccessionGenerator() { + // Do nothing + } } \ No newline at end of file