├── LICENSE ├── README.md ├── docs ├── CNAME ├── _config.yml ├── _layouts │ ├── default.html │ └── post.html ├── _posts │ ├── 2020-03-15-hello-persistent-world.md │ ├── 2020-03-16-jep-352.md │ ├── 2020-03-17-using-jep-352-api.md │ ├── 2020-10-29-narayana.md │ ├── 2020-11-05-infinispan.md │ ├── 2021-01-11-new-year.md │ ├── 2021-04-06-narayana-config.md │ ├── 2021-04-12-debug-logging.md │ ├── 2021-04-14-infinispan-config.md │ ├── 2021-04-16-pmem-in-containers.md │ ├── 2021-06-15-the-lts-cometh.md │ ├── 2021-07-12-testing-java-pmem.md │ ├── 2021-08-03-beta2.md │ └── 2021-09-15-ga-release.md ├── blog │ ├── atom.xml │ └── index.html ├── css │ └── main.css └── index.html ├── logwriting-benchmark ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── mashona │ └── logwriting │ └── perftest │ ├── AppendOnlyLogBenchmark.java │ ├── ArrayStoreBenchmark.java │ ├── MappedFileChannelBenchmark.java │ └── SimpleHardwareBenchmark.java ├── logwriting ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── mashona │ │ └── logwriting │ │ ├── AppendOnlyLog.java │ │ ├── AppendOnlyLogImpl.java │ │ ├── AppendOnlyLogImplConfig.java │ │ ├── AppendOnlyLogWithLocation.java │ │ ├── ArrayStore.java │ │ ├── ArrayStoreImpl.java │ │ ├── MappedFileChannel.java │ │ ├── MappedFileChannelMetadata.java │ │ ├── PersistenceHandle.java │ │ └── PmemUtil.java │ └── test │ ├── java │ └── io │ │ └── mashona │ │ └── logwriting │ │ ├── AppendOnlyLogTests.java │ │ ├── ArrayStoreTests.java │ │ ├── BMObjectAnnotationHandler.java │ │ ├── DummyExtensionContext.java │ │ ├── ExecutionTracer.java │ │ ├── MappedFileChannelMetadataTests.java │ │ ├── MappedFileChannelTests.java │ │ ├── PersistenceHandleTests.java │ │ ├── PmemUtilTests.java │ │ └── WithBytemanFrom.java │ └── resources │ └── logback.xml └── pobj ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── mashona │ └── pobj │ ├── PersistenceProvider.java │ ├── allocator │ ├── CompositeAllocator.java │ ├── CompositeAllocatorPersistence.java │ ├── MemoryHeap.java │ ├── RegionBitmap.java │ ├── RegionBitmapPersistence.java │ └── RegionConfig.java │ ├── runtime │ ├── MemoryBackedObject.java │ └── MemoryOperations.java │ └── transaction │ ├── PersistentTransaction.java │ ├── PersistentTransactionManager.java │ ├── TransactionManager.java │ ├── TransactionStore.java │ ├── TransactionalCompositeAllocator.java │ ├── TransactionalMemoryHeap.java │ ├── TransactionalMemoryOperations.java │ ├── VolatileTransaction.java │ └── events │ ├── BeforeWriteEvent.java │ ├── BeforeWriteEventPersistence.java │ ├── CreateEvent.java │ ├── DeallocateEvent.java │ ├── DeallocateEventPersistence.java │ ├── DeleteEvent.java │ ├── MallocEvent.java │ ├── MallocEventPersistence.java │ ├── OutcomeEvent.java │ ├── OutcomeEventPersistence.java │ ├── TransactionEvent.java │ └── TransactionEventPersistence.java └── test └── java └── io └── mashona └── pobj ├── allocator ├── BogusMemoryBackedObject.java ├── CompositeAllocatorPersistenceTests.java ├── CompositeAllocatorTests.java ├── LargeMemoryBackedObject.java ├── MemoryHeapTests.java ├── RegionBitmapPersistenceTests.java └── RegionBitmapTests.java ├── generated ├── MBOTestEntity.java ├── MemoryOperationsTests.java └── PointImpl.java └── transaction ├── CrashRecoveryTests.java ├── PersistentTransactionWiringTests.java ├── TransactionWiringTests.java └── events ├── TransactionEventPersistenceTests.java └── TransactionEventTests.java /docs/CNAME: -------------------------------------------------------------------------------- 1 | mashona.io -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | name: Mashona, Persistent Memory library for pure Java 2 | markdown: kramdown 3 | permalink: /blog/:year/:month/:day/:title 4 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Note that setting alwaysCheckpoint=true requires also that linearOrdering=true.
32 | *Do not set authoritativeCheckpointOnReads=true when opening an existing log file, unless it was written with alwaysCheckpoint=true.
33 | * 34 | * @param blockPadding true if extra space should be used to increase performance, or false for a slower but more compact record format. 35 | * @param linearOrdering true if strict serial ordering of writes is required, false for more relaxed ordering guarantees. 36 | * @param alwaysCheckpointWrites true if automatic checkpointing of writes is required, false otherwise. 37 | * @param authoritativeCheckpointOnReads true if the persistent checkpoint (limit) in the file should be used when reading back the log, 38 | * false if the entries should be walked instead. 39 | * 40 | * @throws IllegalArgumentException if an unsupported combination of settings is used. 41 | */ 42 | public AppendOnlyLogImplConfig(boolean blockPadding, boolean linearOrdering, 43 | boolean alwaysCheckpointWrites, boolean authoritativeCheckpointOnReads) { 44 | 45 | if(alwaysCheckpointWrites && !linearOrdering) { 46 | throw new IllegalArgumentException("linearOrdering must be true when alwaysCheckpoint is enabled"); 47 | } 48 | 49 | this.blockPadding = blockPadding; 50 | this.linearOrdering = linearOrdering; 51 | this.alwaysCheckpoint = alwaysCheckpointWrites; 52 | this.authoritativeCheckpointOnReads = authoritativeCheckpointOnReads; 53 | } 54 | 55 | /** 56 | * Reports the padding mode. 57 | * 58 | * @return true if extra space should be used to increase performance, or false for a slower but more compact record format. 59 | */ 60 | public boolean isBlockPadding() { 61 | return blockPadding; 62 | } 63 | 64 | /** 65 | * Reports the ordering mode. 66 | * 67 | * @return true if strict serial ordering of writes is required, false for more relaxed ordering guarantees. 68 | */ 69 | public boolean isLinearOrdering() { 70 | return linearOrdering; 71 | } 72 | 73 | /** 74 | * Reports the auto-checkpointing mode. 75 | * 76 | * @return true if writes should always include an implicit checkpoint update, false otherwise. 77 | */ 78 | public boolean isAlwaysCheckpoint() { 79 | return alwaysCheckpoint; 80 | } 81 | 82 | /** 83 | * Reports the read/recovery mode. 84 | * 85 | * @return true if the persistent checkpoint (limit) in the file should be used when reading back the log, 86 | * false if the entries should be walked instead. 87 | */ 88 | public boolean isAuthoritativeCheckpointOnReads() { 89 | return authoritativeCheckpointOnReads; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "AppendOnlyLogImplConfig{" + 95 | "blockPadding=" + blockPadding + 96 | ", linearOrdering=" + linearOrdering + 97 | ", alwaysCheckpoint=" + alwaysCheckpoint + 98 | ", authoritativeCheckpointOnReads=" + authoritativeCheckpointOnReads + 99 | '}'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /logwriting/src/main/java/io/mashona/logwriting/ArrayStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import java.io.IOException; 16 | import java.nio.ByteBuffer; 17 | 18 | /** 19 | * An array-like persistent storage structure with a number of fixed size slots, accessible concurrently. 20 | * The structure is zero-indexed, as with Java arrays or lists. 21 | *22 | * Implementations are expected to provide safe concurrent access to different slots, 23 | * but not required to provide guarantees regarding concurrent access to the same slot. 24 | * 25 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 26 | * @since 2020-04 27 | */ 28 | public interface ArrayStore { 29 | 30 | /** 31 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 32 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 33 | * 34 | * @param slotIndex the location. 35 | * @param data the content. zero length content is equivalent to clearing the slot. 36 | */ 37 | default void write(int slotIndex, ByteBuffer data) throws IOException { 38 | write(slotIndex, data, true); 39 | } 40 | 41 | /** 42 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 43 | * 44 | * @param slotIndex the location. 45 | * @param data the content. zero length content is equivalent to clearing the slot. 46 | * @param force if the change should be immediately persistent or not. 47 | */ 48 | void write(int slotIndex, ByteBuffer data, boolean force) throws IOException; 49 | 50 | /** 51 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 52 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 53 | * 54 | * @param slotIndex the location. 55 | * @param data the content. zero length content is equivalent to clearing the slot. 56 | */ 57 | default void write(int slotIndex, byte[] data) throws IOException { 58 | write(slotIndex, data, true); 59 | } 60 | 61 | /** 62 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 63 | * 64 | * @param slotIndex the location. 65 | * @param data the content. zero length content is equivalent to clearing the slot. 66 | * @param force if the change should be immediately persistent or not. 67 | */ 68 | void write(int slotIndex, byte[] data, boolean force) throws IOException; 69 | 70 | /** 71 | * Read the given slot, returning a copy of its contents. 72 | * 73 | * @param slotIndex the location. 74 | * @return a copy of the content, or null if the slot has not been written or has been cleared. 75 | */ 76 | ByteBuffer readAsByteBuffer(int slotIndex) throws IOException; 77 | 78 | /** 79 | * Read the given slot, returning a copy of its contents. 80 | * 81 | * @param slotIndex the location. 82 | * @return a copy of the content, or null if the slot has not been written or has been cleared. 83 | */ 84 | byte[] readAsByteArray(int slotIndex) throws IOException; 85 | 86 | /** 87 | * Update the given slot, discarding the contents. 88 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 89 | * 90 | * @param slotIndex location. 91 | * @param scrub overwrite the entire slot with zero data, which is slower than simply marking it invalid. 92 | */ 93 | default void clear(int slotIndex, boolean scrub) throws IOException { 94 | clear(slotIndex, scrub, true); 95 | } 96 | 97 | /** 98 | * Update the given slot, discarding the contents. 99 | * 100 | * @param slotIndex location. 101 | * @param scrub overwrite the entire slot with zero data, which is slower than simply marking it invalid. 102 | * @param force if the change should be immediately persistent or not. 103 | */ 104 | void clear(int slotIndex, boolean scrub, boolean force) throws IOException; 105 | 106 | /** 107 | * Close the store, preventing subsequent reads and writes. 108 | */ 109 | void close() throws IOException; 110 | } 111 | -------------------------------------------------------------------------------- /logwriting/src/main/java/io/mashona/logwriting/PersistenceHandle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.jboss.logging.Logger; 16 | 17 | import java.lang.reflect.Field; 18 | import java.nio.MappedByteBuffer; 19 | 20 | /** 21 | * A holder for a reference to a MappedByteBuffer backed by persistent memory, 22 | * via which data ranges may be flushed from cache to the persistence domain. 23 | * 24 | * Users of higher level abstractions should not normally need to use this class directly. 25 | * 26 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 27 | * @since 2019-04 28 | */ 29 | public class PersistenceHandle { 30 | 31 | private static final Logger logger = Logger.getLogger(PersistenceHandle.class); 32 | 33 | private final MappedByteBuffer buffer; 34 | private final int offset; 35 | private final int length; 36 | 37 | /** 38 | * Initializes a new PersistenceHandle for the specified region of the provided buffer. 39 | * 40 | *
Note that the MappedByteBuffer MUST be the original instance obtained 41 | * from fileChannel.map and NOT a duplicate or slice thereof.
42 | * 43 | * @param buffer The MappedByteBuffer over which to operate. 44 | * @param offset the offset in the buffer at which to base our operational area. 45 | * @param length the number of bytes in the operational area. 46 | */ 47 | public PersistenceHandle(MappedByteBuffer buffer, int offset, int length) { 48 | if(logger.isTraceEnabled()) { 49 | logger.tracev("entry with buffer={0}, offset={1}, length={2}", buffer, offset, length); 50 | } 51 | 52 | this.buffer = buffer; 53 | this.offset = offset; 54 | this.length = length; 55 | 56 | if(logger.isTraceEnabled()) { 57 | logger.tracev("exit {0}", this); 58 | } 59 | } 60 | 61 | // mostly for testing. 62 | PersistenceHandle duplicate(int offset, int length) { 63 | if(logger.isTraceEnabled()) { 64 | logger.tracev("entry for {0} with offset={1}, length={2}", this, offset, length); 65 | } 66 | 67 | if (length > this.length) { 68 | IllegalArgumentException illegalArgumentException = 69 | new IllegalArgumentException("Given length of " + length + " exceeds max of " + this.length); 70 | if(logger.isTraceEnabled()) { 71 | logger.tracev(illegalArgumentException, "throwing {0}", illegalArgumentException.toString()); 72 | } 73 | throw illegalArgumentException; 74 | } 75 | 76 | PersistenceHandle persistenceHandle = new PersistenceHandle(buffer, this.offset + offset, length); 77 | 78 | if(logger.isTraceEnabled()) { 79 | logger.tracev("exit returning {0}", persistenceHandle); 80 | } 81 | return persistenceHandle; 82 | } 83 | 84 | /** 85 | * Forces any changes made within the specified area to be written to the persistence domain. 86 | * 87 | * @param from the base offset. 88 | * @param length the number of bytes. 89 | */ 90 | public void persist(int from, int length) { 91 | if(logger.isTraceEnabled()) { 92 | logger.tracev("entry for {0} with from={1}, length={2}", this, from, length); 93 | } 94 | 95 | if (length > this.length) { 96 | IllegalArgumentException illegalArgumentException = 97 | new IllegalArgumentException("Given length of " + length + " exceeds max of " + this.length); 98 | if(logger.isTraceEnabled()) { 99 | logger.tracev(illegalArgumentException, "throwing {0}", illegalArgumentException); 100 | } 101 | throw illegalArgumentException; 102 | } 103 | 104 | buffer.force(from + offset, length); 105 | 106 | if(logger.isTraceEnabled()) { 107 | logger.tracev("exit"); 108 | } 109 | } 110 | 111 | /** 112 | * Forces any changes made to be written to the persistence domain. 113 | */ 114 | public void persist() { 115 | if(logger.isTraceEnabled()) { 116 | logger.tracev("entry for {0}", this); 117 | } 118 | 119 | persist(0, length); 120 | 121 | if(logger.isTraceEnabled()) { 122 | logger.tracev("exit"); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/ArrayStoreTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.CsvSource; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | import java.nio.channels.ClosedChannelException; 25 | import java.util.Arrays; 26 | 27 | import static org.junit.jupiter.api.Assertions.*; 28 | 29 | public class ArrayStoreTests { 30 | 31 | private static final File file = new File(System.getenv("PMEM_TEST_DIR"), "test"); 32 | private static final int NUMBER_OF_SLOTS = 100; 33 | private static final int SLOT_DATA_CAPACITY = 96; 34 | 35 | private ArrayStore arrayStore; 36 | 37 | @BeforeEach 38 | public void setUp() throws IOException { 39 | if (file.exists()) { 40 | file.delete(); 41 | } 42 | 43 | arrayStore = new ArrayStoreImpl(file, NUMBER_OF_SLOTS, SLOT_DATA_CAPACITY); 44 | } 45 | 46 | @AfterEach 47 | public void tearDown() { 48 | if (file.exists()) { 49 | file.delete(); 50 | } 51 | } 52 | 53 | @Test 54 | public void testConfigGetters() { 55 | assertEquals(NUMBER_OF_SLOTS, ((ArrayStoreImpl) arrayStore).getNumberOfSlots()); 56 | assertEquals(SLOT_DATA_CAPACITY, ((ArrayStoreImpl) arrayStore).getSlotDataCapacity()); 57 | } 58 | 59 | @ParameterizedTest 60 | @CsvSource({ 61 | "byte[], byte[]", 62 | "ByteBuffer, byte[]", 63 | "byte[], ByteBuffer", 64 | "ByteBuffer, ByteBuffer" 65 | }) 66 | public void testWriteAndReadBack(String readMethod, String writeMethod) throws IOException { 67 | 68 | for (int i = 0; i < NUMBER_OF_SLOTS; i++) { 69 | // write at least one byte and no more than slot size bytes, preferring to write exactly i bytes. 70 | int length = Math.max(1, Math.min(i, SLOT_DATA_CAPACITY)); 71 | byte[] data = new byte[length]; 72 | Arrays.fill(data, (byte) i); 73 | 74 | switch (writeMethod) { 75 | case "byte[]": 76 | arrayStore.write(i, data); 77 | case "ByteBuffer": 78 | arrayStore.write(i, ByteBuffer.wrap(data)); 79 | } 80 | } 81 | 82 | for (int i = 0; i < NUMBER_OF_SLOTS; i++) { 83 | 84 | byte[] data = null; 85 | switch (readMethod) { 86 | case "byte[]": 87 | data = arrayStore.readAsByteArray(i); 88 | case "ByteBuffer": 89 | ByteBuffer byteBuffer = arrayStore.readAsByteBuffer(i); 90 | data = byteBuffer.array(); // implementation specific shortcut. 91 | } 92 | 93 | int expectedLength = Math.max(1, Math.min(i, SLOT_DATA_CAPACITY)); 94 | assertEquals(expectedLength, data.length); 95 | for (int j = 0; j < expectedLength; j++) { 96 | assertEquals(i, data[j]); 97 | } 98 | } 99 | } 100 | 101 | @Test 102 | public void testClear() throws IOException { 103 | 104 | byte[] data = new byte[]{1}; 105 | arrayStore.write(0, data); 106 | byte[] read = arrayStore.readAsByteArray(0); 107 | assertArrayEquals(data, read); 108 | 109 | arrayStore.clear(0, false); 110 | assertNull(arrayStore.readAsByteArray(0)); 111 | assertNull(arrayStore.readAsByteBuffer(0)); 112 | 113 | arrayStore.write(0, data); 114 | read = arrayStore.readAsByteArray(0); 115 | assertArrayEquals(data, read); 116 | 117 | arrayStore.clear(0, true); 118 | assertNull(arrayStore.readAsByteArray(0)); 119 | assertNull(arrayStore.readAsByteBuffer(0)); 120 | } 121 | 122 | @Test 123 | public void testZeroLengthWrite() throws IOException { 124 | assertNull(arrayStore.readAsByteArray(0)); 125 | arrayStore.write(0, new byte[0]); 126 | assertNull(arrayStore.readAsByteArray(0)); 127 | } 128 | 129 | @Test 130 | public void testWriteTooLong() throws IOException { 131 | assertThrows(IOException.class, () -> arrayStore.write(0, new byte[SLOT_DATA_CAPACITY + 1])); 132 | } 133 | 134 | @Test 135 | public void testIndexOutOfBounds() throws IOException { 136 | assertThrows(ArrayIndexOutOfBoundsException.class, () -> arrayStore.readAsByteArray(-1)); 137 | assertThrows(ArrayIndexOutOfBoundsException.class, () -> arrayStore.readAsByteArray(NUMBER_OF_SLOTS)); 138 | } 139 | 140 | @Test 141 | public void testClosing() throws IOException { 142 | 143 | byte[] data = new byte[]{1}; 144 | arrayStore.write(0, data); 145 | 146 | arrayStore.close(); 147 | 148 | assertThrows(ClosedChannelException.class, () -> arrayStore.write(0, data)); 149 | assertThrows(ClosedChannelException.class, () -> arrayStore.readAsByteArray(0)); 150 | assertThrows(ClosedChannelException.class, () -> arrayStore.clear(0, false)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/BMObjectAnnotationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.jboss.byteman.contrib.bmunit.*; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.extension.*; 19 | import org.junit.platform.commons.support.HierarchyTraversalMode; 20 | 21 | import java.lang.annotation.Annotation; 22 | import java.lang.reflect.Method; 23 | import java.util.Optional; 24 | 25 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; 26 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 27 | 28 | public class BMObjectAnnotationHandler implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback { 29 | 30 | public static BMUnit5AbstractHandler[] HANDLERS = { 31 | new BMUnit5ConfigHandler(), 32 | new BMUnit5MultiScriptHandler(), 33 | new BMUnit5SingleScriptHandler(), 34 | new BMUnit5MultiRuleHandler(), 35 | new BMUnit5SingleRuleHandler() 36 | }; 37 | 38 | @Override 39 | public void afterEach(ExtensionContext context) throws Exception { 40 | invokeOnTarget(context, AfterEach.class); 41 | } 42 | 43 | @Override 44 | public void beforeEach(ExtensionContext context) throws Exception { 45 | invokeOnTarget(context, BeforeEach.class); 46 | } 47 | 48 | private void invokeOnTarget(ExtensionContext context, Class extends Annotation> annotationType) throws Exception { 49 | 50 | final Class> testClass = context.getRequiredTestClass(); 51 | final Optional