├── doc ├── JACIS-Presentation.odp ├── JACIS-Presentation.pdf └── images │ ├── ACID-Atomicity.png │ ├── ACID-Consistency.png │ ├── ACID-Durability.png │ ├── ACID-Isolation.png │ ├── JACIS-TrackedView1.png │ ├── JACIS-TrackedView2.png │ ├── JACIS-Concept-Step1.png │ ├── JACIS-Concept-Step2.png │ ├── JACIS-Concept-Step3.png │ ├── JACIS-Concept-Step4.png │ ├── JACIS-Concept-Step5.png │ ├── JACIS-Concept-Step6.png │ ├── TX-Isolation-Levels.png │ ├── TX-OptimisticLocking.png │ ├── TX-PrivateWorkspace.png │ ├── TX-Isolation-DirtyRead.png │ ├── TX-Isolation-LostUpdate.png │ ├── JACIS-TransactionAdapter.png │ ├── TX-Isolation-PhantomRead.png │ ├── JACIS-Extension-Microstream.png │ └── TX-Isolation-NonRepeatableRead.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .settings ├── org.eclipse.jdt.ui.prefs └── org.eclipse.buildship.core.prefs ├── etc └── spellCheckDictionary.dict ├── src ├── main │ ├── java │ │ ├── overview.adoc │ │ ├── overview.html │ │ └── org │ │ │ └── jacis │ │ │ ├── plugin │ │ │ ├── dirtycheck │ │ │ │ ├── object │ │ │ │ │ ├── JacisDirtyTrackingObject.java │ │ │ │ │ └── AbstractReadOnlyModeAndDirtyCheckSupportingObject.java │ │ │ │ ├── StoreEntryBasedDirtyCheck.java │ │ │ │ └── JacisDirtyCheck.java │ │ │ ├── objectadapter │ │ │ │ ├── immutable │ │ │ │ │ └── JacisImmutableObjectAdapter.java │ │ │ │ ├── cloning │ │ │ │ │ ├── JacisCloningObjectAdapterUnsafe.java │ │ │ │ │ ├── JacisCloneable.java │ │ │ │ │ ├── JacisCloningObjectAdapter.java │ │ │ │ │ └── AbstractJacisCloningObjectAdapter.java │ │ │ │ ├── serialization │ │ │ │ │ ├── JacisSerializationObjectAdapter.java │ │ │ │ │ └── JacisJavaSerializationObjectAdapter.java │ │ │ │ └── JacisObjectAdapter.java │ │ │ ├── readonly │ │ │ │ ├── DefaultJacisStoreEntryReadOnlyModeAdapter.java │ │ │ │ ├── object │ │ │ │ │ ├── JacisReadonlyModeSupport.java │ │ │ │ │ └── AbstractReadOnlyModeSupportingObject.java │ │ │ │ └── JacisStoreEntryReadOnlyModeAdapter.java │ │ │ ├── JacisTransactionListenerAdapter.java │ │ │ ├── txadapter │ │ │ │ ├── JacisTransactionAdapter.java │ │ │ │ └── local │ │ │ │ │ ├── JacisTransactionAdapterLocal.java │ │ │ │ │ └── JacisLocalTransaction.java │ │ │ ├── persistence │ │ │ │ └── JacisPersistenceAdapter.java │ │ │ ├── JacisModificationListener.java │ │ │ └── JacisTransactionListener.java │ │ │ ├── exception │ │ │ ├── ReadOnlyException.java │ │ │ ├── JacisInternalException.java │ │ │ ├── JacisTransactionException.java │ │ │ ├── JacisNoTransactionException.java │ │ │ ├── JacisTransactionAlreadyStartedException.java │ │ │ ├── JacisTransactionAlreadyPreparedForCommitException.java │ │ │ ├── JacisUniqueIndexViolationException.java │ │ │ ├── ReadOnlyModeNotSupportedException.java │ │ │ ├── JacisTxCommitException.java │ │ │ ├── JacisTxRollbackException.java │ │ │ ├── JacisModificationVetoException.java │ │ │ ├── JacisStaleObjectException.java │ │ │ ├── JacisTrackedViewModificationException.java │ │ │ └── JacisModificationListenerException.java │ │ │ ├── JacisApi.java │ │ │ ├── store │ │ │ ├── JacisReadOnlyTransactionContext.java │ │ │ ├── KeyValuePair.java │ │ │ ├── JacisStoreAdminInterface.java │ │ │ ├── TrackedViewTransactionLocal.java │ │ │ └── StoreEntry.java │ │ │ ├── trackedviews │ │ │ ├── TrackedViewClustered.java │ │ │ └── TrackedView.java │ │ │ ├── container │ │ │ ├── JacisContainerReadOnlyTransactionContext.java │ │ │ └── JacisTransactionHandle.java │ │ │ ├── extension │ │ │ ├── persistence │ │ │ │ ├── microstream │ │ │ │ │ ├── MicrostreamRoot.java │ │ │ │ │ ├── MicrostreamStoreEntity.java │ │ │ │ │ ├── MicrostreamStoreRoot.java │ │ │ │ │ └── MicrostreamStorage.java │ │ │ │ └── xodus │ │ │ │ │ └── XodusPersistenceAdapter.java │ │ │ └── objectadapter │ │ │ │ └── cloning │ │ │ │ └── microstream │ │ │ │ ├── JacisMicrostreamCloningObjectAdapter.java │ │ │ │ └── MicrostreamObjectCopier.java │ │ │ ├── index │ │ │ ├── AbstractJacisMultiIndex.java │ │ │ ├── AbstractJacisIndex.java │ │ │ ├── JacisIndexRegistryTxView.java │ │ │ └── JacisUniqueIndex.java │ │ │ └── util │ │ │ └── ConcurrentHashSet.java │ └── resources │ │ └── logback.xml.template └── test │ ├── java │ └── org │ │ └── jacis │ │ ├── testhelper │ │ ├── FileUtils.java │ │ ├── TrackedTestView.java │ │ ├── TestObjectWithoutReadOnlyMode.java │ │ └── TestObject.java │ │ ├── trackedview │ │ └── TransactionLocalTrackedViewTest.java │ │ ├── objectadapter │ │ ├── serialization │ │ │ ├── JacisStoreWithSerializationTrackedViewTest.java │ │ │ └── JacisStoreWithSerializationMultiThreadedTest.java │ │ └── cloning │ │ │ ├── JacisStoreWithCloningTrackedViewTest.java │ │ │ └── JacisStoreWithCloningMultithreadedTest.java │ │ ├── JacisContainerTest.java │ │ ├── performance │ │ └── JacisPerformanceTest.java │ │ ├── modificationlistener │ │ └── JacisModificationListenerTest.java │ │ ├── index │ │ └── JacisNonUniqueMultiIndexTest.java │ │ ├── readonlytxview │ │ └── ReadOnlyTxViewTest.java │ │ └── JacisContainerLocalTxTest.java │ └── resources │ └── logback.xml ├── .gitignore ├── .project ├── README.md ├── gradle.properties ├── .classpath ├── .github └── workflows │ ├── codeql-analysis.yml │ └── buildDeploy.yml ├── gradlew.bat └── CODE_OF_CONDUCT.md /doc/JACIS-Presentation.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/JACIS-Presentation.odp -------------------------------------------------------------------------------- /doc/JACIS-Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/JACIS-Presentation.pdf -------------------------------------------------------------------------------- /doc/images/ACID-Atomicity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/ACID-Atomicity.png -------------------------------------------------------------------------------- /doc/images/ACID-Consistency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/ACID-Consistency.png -------------------------------------------------------------------------------- /doc/images/ACID-Durability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/ACID-Durability.png -------------------------------------------------------------------------------- /doc/images/ACID-Isolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/ACID-Isolation.png -------------------------------------------------------------------------------- /doc/images/JACIS-TrackedView1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-TrackedView1.png -------------------------------------------------------------------------------- /doc/images/JACIS-TrackedView2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-TrackedView2.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step1.png -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step2.png -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step3.png -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step4.png -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step5.png -------------------------------------------------------------------------------- /doc/images/JACIS-Concept-Step6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Concept-Step6.png -------------------------------------------------------------------------------- /doc/images/TX-Isolation-Levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-Isolation-Levels.png -------------------------------------------------------------------------------- /doc/images/TX-OptimisticLocking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-OptimisticLocking.png -------------------------------------------------------------------------------- /doc/images/TX-PrivateWorkspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-PrivateWorkspace.png -------------------------------------------------------------------------------- /doc/images/TX-Isolation-DirtyRead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-Isolation-DirtyRead.png -------------------------------------------------------------------------------- /doc/images/TX-Isolation-LostUpdate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-Isolation-LostUpdate.png -------------------------------------------------------------------------------- /doc/images/JACIS-TransactionAdapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-TransactionAdapter.png -------------------------------------------------------------------------------- /doc/images/TX-Isolation-PhantomRead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-Isolation-PhantomRead.png -------------------------------------------------------------------------------- /doc/images/JACIS-Extension-Microstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/JACIS-Extension-Microstream.png -------------------------------------------------------------------------------- /doc/images/TX-Isolation-NonRepeatableRead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanWiemer/jacis/HEAD/doc/images/TX-Isolation-NonRepeatableRead.png -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | formatter_profile=_JACIS-Formatting 3 | formatter_settings_version=20 4 | -------------------------------------------------------------------------------- /etc/spellCheckDictionary.dict: -------------------------------------------------------------------------------- 1 | demarcation 2 | rollback 3 | jan 4 | wiemer 5 | jacis 6 | transactional 7 | deserialization 8 | timestamp 9 | microstream 10 | lifecycle 11 | atomicity 12 | versioning 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home= 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=false 12 | show.console.view=false 13 | show.executions.view=false 14 | -------------------------------------------------------------------------------- /src/main/java/overview.adoc: -------------------------------------------------------------------------------- 1 | = Java ACI Store - Transient and transactional store for Java objects. 2 | 3 | The name of the store is derived from the acronym ACID (Atomicity, Consistency, Isolation, Durability) describing the properties of transactions. 4 | The store is designed to fulfill the first three of these properties but not the Durability. 5 | 6 | For more information see https://github.com/JanWiemer/jacis/wiki 7 | -------------------------------------------------------------------------------- /src/main/java/overview.html: -------------------------------------------------------------------------------- 1 |

Java ACI Store - Transient and transactional store for Java objects.

2 | 3 | The name of the store is derived from the acronym ACID (Atomicity, Consistency, Isolation, Durability) describing the properties of transactions. 4 | The store is designed to fulfill the first three of these properties but not the Durability. 5 | 6 | For more information see https://github.com/JanWiemer/jacis/wiki 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dmp 2 | *.hprof 3 | *.iml 4 | *.log 5 | *.log.zip 6 | *.tmp 7 | .DS_Store 8 | .gradle 9 | .idea 10 | .metadata 11 | .settings 12 | /src/main/resources/logback.xml 13 | /storage/backup 14 | /build 15 | /deploy 16 | /target 17 | /dist 18 | /tmp 19 | /var 20 | java_pid*.* 21 | junit*.properties 22 | log 23 | test-output 24 | ivysettings-local.xml 25 | /bin/ 26 | .classpath 27 | .project 28 | /.profileconfig.json 29 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/dirtycheck/object/JacisDirtyTrackingObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.dirtycheck.object; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Interface for an object automatically tracking if it is dirty. 11 | */ 12 | @JacisApi 13 | public interface JacisDirtyTrackingObject { 14 | 15 | /** @return if the object is dirty */ 16 | boolean isDirty(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/ReadOnlyException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Thrown when trying to modify an object in read only mode. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @JacisApi 15 | public class ReadOnlyException extends IllegalStateException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | public ReadOnlyException(String s) { 20 | super(s); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisInternalException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown if an internal Jacis Error happens. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @JacisApi 15 | public class JacisInternalException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | public JacisInternalException(String message) { 20 | super(message); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/testhelper/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | package org.jacis.testhelper; 5 | 6 | import java.io.File; 7 | 8 | public abstract class FileUtils { 9 | 10 | public static boolean deleteDirectory(File directoryToBeDeleted) { 11 | File[] allContents = directoryToBeDeleted.listFiles(); 12 | if (allContents != null) { 13 | for (File file : allContents) { 14 | deleteDirectory(file); 15 | } 16 | } 17 | return directoryToBeDeleted.delete(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/JacisApi.java: -------------------------------------------------------------------------------- 1 | package org.jacis; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * The public API of the classes annotated with this annotation belong the JACIS API. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @Documented 15 | @Retention(RetentionPolicy.CLASS) 16 | @Target(ElementType.TYPE) 17 | public @interface JacisApi { 18 | // empty 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTransactionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown in case of internal problems dealing with an external transaction system. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @JacisApi 15 | public class JacisTransactionException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | public JacisTransactionException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisNoTransactionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown if an operation is called on a store that requires to run inside a transaction, 11 | * but there is no transaction is active. 12 | * 13 | * @author Jan Wiemer 14 | */ 15 | @JacisApi 16 | public class JacisNoTransactionException extends RuntimeException { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | public JacisNoTransactionException(String message) { 21 | super(message); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jacis 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTransactionAlreadyStartedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown on the attempt to start a new (local) transaction 11 | * while there is already a transaction active. 12 | * 13 | * @author Jan Wiemer 14 | */ 15 | @JacisApi 16 | public class JacisTransactionAlreadyStartedException extends RuntimeException { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | public JacisTransactionAlreadyStartedException(String message) { 21 | super(message); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTransactionAlreadyPreparedForCommitException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown on the attempt to update an object 11 | * while the current transaction is already prepared for commit. 12 | * 13 | * @author Jan Wiemer 14 | */ 15 | @JacisApi 16 | public class JacisTransactionAlreadyPreparedForCommitException extends RuntimeException { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | public JacisTransactionAlreadyPreparedForCommitException(String message) { 21 | super(message); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisUniqueIndexViolationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Thrown when trying to commit an object that would violate a unique index 11 | * (or registering a unique index that would be violated by the existing objects). 12 | * 13 | * @author Jan Wiemer 14 | */ 15 | @JacisApi 16 | public class JacisUniqueIndexViolationException extends IllegalStateException { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | public JacisUniqueIndexViolationException(String s) { 21 | super(s); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/ReadOnlyModeNotSupportedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Thrown when trying to access an object in read only mode when the object adapter 11 | * does not support a read only mode for this object 12 | * and is configured to throw an exception in this case. 13 | * 14 | * @author Jan Wiemer 15 | */ 16 | @JacisApi 17 | public class ReadOnlyModeNotSupportedException extends IllegalStateException { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | public ReadOnlyModeNotSupportedException(String s) { 22 | super(s); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTxCommitException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception attached to an exception thrown during commit to hold some additional information regarding the committed JACIS transaction. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @JacisApi 15 | public class JacisTxCommitException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | public JacisTxCommitException(String message) { 20 | super(message); 21 | } 22 | 23 | public JacisTxCommitException(String message, Throwable cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTxRollbackException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception attached to an exception thrown during commit to hold some additional information regarding the committed JACIS transaction. 11 | * 12 | * @author Jan Wiemer 13 | */ 14 | @JacisApi 15 | public class JacisTxRollbackException extends RuntimeException { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | public JacisTxRollbackException(String message) { 20 | super(message); 21 | } 22 | 23 | public JacisTxRollbackException(String message, Throwable cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Java 8](https://github.com/JanWiemer/jacis/workflows/JACIS-CI-Build/badge.svg)](https://github.com/JanWiemer/jacis/actions?query=workflow%3AJACIS-CI-Build) 2 | ![CodeQL](https://github.com/JanWiemer/jacis/workflows/CodeQL/badge.svg) 3 | 4 | # jacis 5 | Java ACI Store - Transient and transactional store for Java objects. 6 | 7 | The name of the store is derived from the acronym ACID (Atomicity, Consistency, Isolation, Durability) describing the properties of transactions. The store is designed to fulfill the first three of these properties but not the Durability. 8 | 9 | The JACIS project follows the semantic versioning policy (see https://semver.org/, API indicated by ``JacisApi`` Annotation). 10 | 11 | For more information see https://github.com/JanWiemer/jacis/wiki 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ############################ 2 | # JACIS VERSION: 3 | ############################ 4 | version=2.1.20 5 | #=format for release: 2.1.25 6 | #=format for snapshot: 2.0.26-2023-01-01 (on the way to 2.0.26) 7 | # 8 | ############################ 9 | # DEPENDENCIES 10 | ############################ 11 | version_slf4j=2.0.16 12 | #= Important Note for logback: 13 | #=* logback 1.3.x compatible with JDK 8 and javax.* namespace 14 | #=* logback 1.4.x compatible with JDK >=11 and jakarta.* namespace 15 | version_logback=1.5.18 16 | version_junit=4.13.2 17 | # 18 | ############################ 19 | # EXTENSIONS 20 | ############################ 21 | version_eclipse_store=1.3.1 22 | version_xodus=2.0.1 23 | version_jackson=2.17.0 24 | # 25 | ########################### 26 | # MISC 27 | ############################ 28 | #org.gradle.warning.mode=all 29 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisModificationVetoException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.plugin.JacisModificationListener; 9 | 10 | /** 11 | * Thrown when a {@link JacisModificationListener} has a veto against a tracked modification during the prepare phase. 12 | * In this case it can throw this exception to roll back the whole transaction 13 | * 14 | * @author Jan Wiemer 15 | */ 16 | @JacisApi 17 | public class JacisModificationVetoException extends IllegalStateException { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | public JacisModificationVetoException(JacisModificationListener modListener, String message) { 22 | super("Modification veto by "+modListener.getClass().getSimpleName()+": "+message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/dirtycheck/StoreEntryBasedDirtyCheck.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.dirtycheck; 6 | 7 | import org.jacis.plugin.dirtycheck.object.JacisDirtyTrackingObject; 8 | 9 | /** 10 | * Implementation of the {@link JacisDirtyCheck} that is based on objects 11 | * tracking their dirty state and provide this state via the {@link JacisDirtyTrackingObject#isDirty()} method. 12 | * 13 | * @param Key type of the store entry 14 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 15 | * @author Jan Wiemer 16 | */ 17 | public class StoreEntryBasedDirtyCheck implements JacisDirtyCheck { 18 | 19 | @Override 20 | public boolean isDirty(K key, TV originalValue, TV currentValue) { 21 | if(currentValue==null) { 22 | return originalValue!=null; 23 | } else { 24 | return currentValue.isDirty(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/store/JacisReadOnlyTransactionContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.store; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Representing a read only version of the context of a store within a transaction. 11 | * The context contains all transactional views for objects in the store belonging to this transaction. 12 | * The context can be used to propagate a read only view of this context to another transaction in another thread. 13 | * Note that it is only possible to propagate a read only view to prevent multiple threads to work in a single transaction concurrently. 14 | * 15 | * @author Jan Wiemer 16 | */ 17 | @JacisApi 18 | public interface JacisReadOnlyTransactionContext { 19 | 20 | /** 21 | * @return The id of the transaction this transactional context was retrieved from. 22 | */ 23 | String getTxId(); 24 | 25 | /** 26 | * @return The id of the transaction this transactional context should be used in read only mode. 27 | */ 28 | String getReadOnlyTxId(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/trackedviews/TrackedViewClustered.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.trackedviews; 6 | 7 | import java.util.Collection; 8 | 9 | import org.jacis.JacisApi; 10 | 11 | /** 12 | * A clustered tracked view provides access to sub views by a key. 13 | * The advantage is that only the desired sub view is cloned when accessing it. 14 | * 15 | * @param The type of the original values / objects from the store 16 | * @param The key type for the sub views 17 | * @param The type of the sub views 18 | * @author Jan Wiemer 19 | */ 20 | @JacisApi 21 | public interface TrackedViewClustered> extends TrackedView { 22 | 23 | /** 24 | * Return the partition / cluster of the clustered tracked view for the passed key. 25 | * Note that the method must not return null for any valid sub view key! 26 | * 27 | * @param key The key for the desired sub view (/ partition / cluster) 28 | * @return the partition / cluster of the clustered tracked view for the passed key. 29 | */ 30 | SVT getSubView(SVK key); 31 | 32 | /** @return All keys that can be used to access one partition / cluster of the clustered tracked view. */ 33 | Collection getSubViewKeys(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/container/JacisContainerReadOnlyTransactionContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.container; 6 | 7 | import java.util.AbstractMap; 8 | import java.util.AbstractMap.SimpleImmutableEntry; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import org.jacis.JacisApi; 13 | import org.jacis.store.JacisReadOnlyTransactionContext; 14 | import org.jacis.store.JacisStore; 15 | 16 | /** 17 | * Representing a read only version of all the contexts of the stores 18 | * within a transaction belonging to the container. 19 | * 20 | * @author Jan Wiemer 21 | */ 22 | @JacisApi 23 | public class JacisContainerReadOnlyTransactionContext { 24 | 25 | private final List, JacisReadOnlyTransactionContext>> storeContextList = new ArrayList<>(); 26 | 27 | void add(JacisStore store, JacisReadOnlyTransactionContext context) { 28 | storeContextList.add(new SimpleImmutableEntry<>(store, context)); 29 | } 30 | 31 | void startReadOnlyTransactionWithContext() { 32 | for (SimpleImmutableEntry, JacisReadOnlyTransactionContext> entry : storeContextList) { 33 | entry.getKey().startReadOnlyTransactionWithContext(entry.getValue()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/dirtycheck/object/AbstractReadOnlyModeAndDirtyCheckSupportingObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.dirtycheck.object; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.exception.ReadOnlyException; 9 | import org.jacis.plugin.readonly.object.AbstractReadOnlyModeSupportingObject; 10 | 11 | /** 12 | * Abstract base class for objects supporting switching them between the usual read-write mode and a read-only mode 13 | * (see {@link AbstractReadOnlyModeSupportingObject}) 14 | * and tracking if the object is dirty by tracking if the {@link #checkWritable()} method has been called. 15 | * 16 | * @author Jan Wiemer 17 | */ 18 | @JacisApi 19 | public abstract class AbstractReadOnlyModeAndDirtyCheckSupportingObject extends AbstractReadOnlyModeSupportingObject implements JacisDirtyTrackingObject { 20 | 21 | private transient boolean dirty = false; 22 | 23 | /** @return if the object is dirty */ 24 | @Override 25 | public boolean isDirty() { 26 | return dirty; 27 | } 28 | 29 | @Override 30 | protected void checkWritable() throws ReadOnlyException { 31 | super.checkWritable(); 32 | dirty = true; 33 | } 34 | 35 | @Override 36 | public void switchToReadOnlyMode() { 37 | super.switchToReadOnlyMode(); 38 | dirty = false; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/persistence/microstream/MicrostreamRoot.java: -------------------------------------------------------------------------------- 1 | package org.jacis.extension.persistence.microstream; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.jacis.container.JacisContainer.StoreIdentifier; 7 | import org.jacis.store.JacisStore; 8 | 9 | /** 10 | * The root object stored by the Microstream storage manager. 11 | * Basically it stores the list of the roots for the Jacis stores. 12 | * 13 | * @author Jan Wiemer 14 | */ 15 | class MicrostreamRoot { 16 | 17 | /** Map containing the root object for each JACIS store. */ 18 | private final Map> storeRootMap = new HashMap<>(); 19 | 20 | @Override 21 | public String toString() { 22 | return getClass().getSimpleName() + "(" + storeRootMap.values() + ")"; 23 | } 24 | 25 | Map> getStoreRootMap() { 26 | return storeRootMap; 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | public MicrostreamStoreRoot getStoreRoot(JacisStore store) { 31 | StoreIdentifier storeIdentifier = store.getStoreIdentifier(); 32 | return (MicrostreamStoreRoot) storeRootMap.get(storeIdentifier); 33 | } 34 | 35 | public void setStoreRoot(JacisStore store, MicrostreamStoreRoot root) { 36 | StoreIdentifier storeIdentifier = store.getStoreIdentifier(); 37 | storeRootMap.put(storeIdentifier, root); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/immutable/JacisImmutableObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.immutable; 6 | 7 | import org.jacis.plugin.objectadapter.JacisObjectAdapter; 8 | 9 | /** 10 | * Implementation of the {@link org.jacis.plugin.objectadapter.JacisObjectAdapter} that can be used if the store only contains 11 | * immutable objects. Note that this requires that the objects are never changed once they are stored in the store. 12 | *

13 | * When using this adapter it is recommended to programmatically ensure that the objects are immutable, e.g. by making all properties final. 14 | * 15 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 16 | * @author Jan Wiemer 17 | */ 18 | public class JacisImmutableObjectAdapter implements JacisObjectAdapter { 19 | 20 | @Override 21 | public V cloneCommitted2WritableTxView(V value) { 22 | return value; 23 | } 24 | 25 | @Override 26 | public V cloneTxView2Committed(V value) { 27 | return value; 28 | } 29 | 30 | @Override 31 | public V cloneCommitted2ReadOnlyTxView(V value) { 32 | return value; 33 | } 34 | 35 | @Override 36 | public V cloneTxView2ReadOnlyTxView(V value) { 37 | return value; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return getClass().getSimpleName(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/readonly/DefaultJacisStoreEntryReadOnlyModeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.readonly; 6 | 7 | import org.jacis.plugin.readonly.object.JacisReadonlyModeSupport; 8 | 9 | /** 10 | * The default implementation of the interface {@link JacisStoreEntryReadOnlyModeAdapter}. 11 | * This implementation is applicable for objects 12 | * implementing the {@link org.jacis.plugin.readonly.object.JacisReadonlyModeSupport} interface 13 | * and uses the methods declared in this interface for switching the mode. 14 | * 15 | * @param The type of the values that should be switched between read-write and read-only mode. 16 | * @author Jan Wiemer 17 | */ 18 | public class DefaultJacisStoreEntryReadOnlyModeAdapter implements JacisStoreEntryReadOnlyModeAdapter { 19 | 20 | @Override 21 | public boolean isApplicableTo(V value) { 22 | return value instanceof JacisReadonlyModeSupport; 23 | } 24 | 25 | @Override 26 | public boolean isReadOnly(V value) { 27 | return ((JacisReadonlyModeSupport) value).isReadOnly(); 28 | } 29 | 30 | @Override 31 | public V switchToReadOnlyMode(V value) { 32 | ((JacisReadonlyModeSupport) value).switchToReadOnlyMode(); 33 | return value; 34 | } 35 | 36 | @Override 37 | public V switchToReadWriteMode(V value) { 38 | ((JacisReadonlyModeSupport) value).switchToReadWriteMode(); 39 | return value; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return getClass().getSimpleName(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%25.25thread] - %msg \(%C{0}.java:%L\)%n 12 | 13 | 14 | 15 | 16 | log/jacis.log 17 | 18 | 19 | log/jacis-%d{yyyy-MM-dd}.%3i.log.zip 20 | 21 | 10MB 22 | 23 | 100 24 | 25 | 26 | %-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%25.25thread] - %msg \(%C{0}.java:%L\)%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml.template: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%25.25thread] - %msg \(%C{0}.java:%L\)%n 12 | 13 | 14 | 15 | 16 | log/jacis.log 17 | 18 | 19 | log/jacis-%d{yyyy-MM-dd}.%i.log.zip 20 | 21 | 100MB 22 | 23 | 10 24 | 25 | 26 | %-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%25.25thread] - %msg \(%C{0}.java:%L\)%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisStaleObjectException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * Exception thrown if an object modified in this transaction has meanwhile been modified by another transaction. 11 | * In order to detect such a situation the store maintains a version counter for each (committed) version of an object. 12 | * Once an object is cloned into a transactional view the current version counter is also stored as original version 13 | * of the transactional view. Each time a transactional object is cloned back to the store of committed objects (during commit) 14 | * the version counter of the committed object is incremented. Before committing an object it is checked if the 15 | * version counter of the committed version is the same as the original version of the transactional view to commit. 16 | * If both version counters are the same the object has not been changed in the meantime, otherwise this exception is thrown 17 | * 18 | * @author Jan Wiemer 19 | */ 20 | @JacisApi 21 | public class JacisStaleObjectException extends RuntimeException { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | /** detail message describing the reason for the stale object exception */ 26 | private String details; 27 | 28 | public JacisStaleObjectException(String message) { 29 | super(message); 30 | } 31 | 32 | public String getDetails() { 33 | return details; 34 | } 35 | 36 | public JacisStaleObjectException setDetails(String details) { 37 | this.details = details; 38 | return this; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/trackedview/TransactionLocalTrackedViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.trackedview; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import org.jacis.container.JacisContainer; 10 | import org.jacis.store.JacisStore; 11 | import org.jacis.testhelper.JacisTestHelper; 12 | import org.jacis.testhelper.TestObject; 13 | import org.jacis.testhelper.TrackedTestView; 14 | import org.jacis.trackedviews.TrackedView; 15 | import org.junit.Test; 16 | 17 | public class TransactionLocalTrackedViewTest { 18 | 19 | @Test 20 | public void testModificationTrackingInTransaction() { 21 | JacisTestHelper testHelper = new JacisTestHelper(); 22 | JacisStore store = testHelper.createTestStoreWithCloning(); 23 | JacisContainer container = store.getContainer(); 24 | container.withLocalTx(() -> { 25 | store.update("1", new TestObject("A1").setValue(5)); 26 | }); 27 | TrackedView view = new TrackedTestView(); 28 | store.getTrackedViewRegistry().registerTrackedView(view); 29 | assertEquals(5, store.getTrackedViewRegistry().getView(TrackedTestView.class).getSum()); 30 | 31 | container.withLocalTx(() -> { 32 | TestObject o = store.get("1"); 33 | o.setValue(3); 34 | store.update("1", o); 35 | assertEquals(3, store.getTrackedViewRegistry().getView(TrackedTestView.class).getSum()); 36 | o.setValue(4); 37 | store.update("1", o); 38 | assertEquals(4, store.getTrackedViewRegistry().getView(TrackedTestView.class).getSum()); 39 | }); 40 | assertEquals(4, store.getTrackedViewRegistry().getView(TrackedTestView.class).getSum()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/JacisTransactionListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer; 9 | import org.jacis.container.JacisTransactionHandle; 10 | 11 | /** 12 | * Adapter class to simplify creating an implementation of the {@link JacisTransactionListener} interface. 13 | * A class implementing this interface can extend this class providing empty default implementation for 14 | * all methods declared in the interface. So the implementation only has to overwrite the desired method and skip the other ones. 15 | * 16 | * @author Jan Wiemer 17 | */ 18 | @Deprecated // no longer needed because interface has default implementations 19 | @JacisApi 20 | public abstract class JacisTransactionListenerAdapter implements JacisTransactionListener { 21 | 22 | @Override 23 | public void beforePrepare(JacisContainer container, JacisTransactionHandle tx) { 24 | // empty 25 | } 26 | 27 | @Override 28 | public void afterPrepare(JacisContainer container, JacisTransactionHandle tx) { 29 | // empty 30 | } 31 | 32 | @Override 33 | public void beforeCommit(JacisContainer container, JacisTransactionHandle tx) { 34 | // empty 35 | } 36 | 37 | @Override 38 | public void afterCommit(JacisContainer container, JacisTransactionHandle tx) { 39 | // empty 40 | } 41 | 42 | @Override 43 | public void beforeRollback(JacisContainer container, JacisTransactionHandle tx) { 44 | // empty 45 | } 46 | 47 | @Override 48 | public void afterRollback(JacisContainer container, JacisTransactionHandle tx) { 49 | // empty 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return getClass().getSimpleName(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/store/KeyValuePair.java: -------------------------------------------------------------------------------- 1 | package org.jacis.store; 2 | 3 | import org.jacis.JacisApi; 4 | 5 | import java.io.Serializable; 6 | 7 | @JacisApi 8 | public class KeyValuePair implements Serializable { 9 | 10 | public static KeyValuePair of(K key, TV val) { 11 | return new KeyValuePair<>(key, val); 12 | } 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | private final K key; 17 | private final TV val; 18 | 19 | public KeyValuePair(K first, TV second) { 20 | this.key = first; 21 | this.val = second; 22 | } 23 | 24 | public K getKey() { 25 | return key; 26 | } 27 | 28 | public TV getVal() { 29 | return val; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "(" + key + ", " + val + ")"; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | final int prime = 31; 40 | int result = 1; 41 | result = prime * result + ((key == null) ? 0 : key.hashCode()); 42 | result = prime * result + ((val == null) ? 0 : val.hashCode()); 43 | return result; 44 | } 45 | 46 | @SuppressWarnings("rawtypes") 47 | @Override 48 | public boolean equals(Object obj) { 49 | if (this == obj) { 50 | return true; 51 | } 52 | if (obj == null) { 53 | return false; 54 | } 55 | if (getClass() != obj.getClass()) { 56 | return false; 57 | } 58 | KeyValuePair other = (KeyValuePair) obj; 59 | if (key == null) { 60 | if (other.key != null) { 61 | return false; 62 | } 63 | } else if (!key.equals(other.key)) { 64 | return false; 65 | } 66 | if (val == null) { 67 | return other.val == null; 68 | } else { 69 | return val.equals(other.val); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/objectadapter/cloning/microstream/JacisMicrostreamCloningObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.extension.objectadapter.cloning.microstream; 6 | 7 | import java.io.Serializable; 8 | 9 | import org.jacis.plugin.objectadapter.cloning.AbstractJacisCloningObjectAdapter; 10 | import org.jacis.plugin.readonly.DefaultJacisStoreEntryReadOnlyModeAdapter; 11 | import org.jacis.plugin.readonly.JacisStoreEntryReadOnlyModeAdapter; 12 | 13 | /** 14 | * Generic implementation of the {@link org.jacis.plugin.objectadapter.JacisObjectAdapter} copying the objects 15 | * to and from the transactional view by means of Microstream serialization. 16 | * 17 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 18 | * @author Jan Wiemer 19 | */ 20 | public class JacisMicrostreamCloningObjectAdapter extends AbstractJacisCloningObjectAdapter { 21 | 22 | private final MicrostreamObjectCopier copier = MicrostreamObjectCopier.New(); 23 | 24 | /** 25 | * Create a cloning object adapter with the passed read only mode adapter. 26 | * 27 | * @param readOnlyModeAdapters Adapter to switch an object between read-only and read-write mode (if supported). 28 | */ 29 | public JacisMicrostreamCloningObjectAdapter(JacisStoreEntryReadOnlyModeAdapter readOnlyModeAdapters) { 30 | super(readOnlyModeAdapters); 31 | } 32 | 33 | /** 34 | * Create a cloning object adapter with a default read only mode adapter (see {@link DefaultJacisStoreEntryReadOnlyModeAdapter}). 35 | */ 36 | public JacisMicrostreamCloningObjectAdapter() { 37 | super(); 38 | } 39 | 40 | @Override 41 | protected V cloneValue(V value) { 42 | return copier.copy(value); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/objectadapter/serialization/JacisStoreWithSerializationTrackedViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.objectadapter.serialization; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import org.jacis.container.JacisContainer; 10 | import org.jacis.store.JacisStore; 11 | import org.jacis.testhelper.JacisTestHelper; 12 | import org.jacis.testhelper.TestObject; 13 | import org.jacis.testhelper.TrackedTestView; 14 | import org.jacis.trackedviews.TrackedView; 15 | import org.junit.Test; 16 | 17 | public class JacisStoreWithSerializationTrackedViewTest { 18 | 19 | @Test 20 | public void testTrackedView() { 21 | JacisTestHelper testHelper = new JacisTestHelper(); 22 | JacisStore store = testHelper.createTestStoreWithSerialization(); 23 | JacisContainer container = store.getContainer(); 24 | container.withLocalTx(() -> { 25 | store.update("1", new TestObject("A1")); 26 | store.update("toDelete1", new TestObject("A1")); 27 | }); 28 | TrackedView view = new TrackedTestView(); 29 | store.getTrackedViewRegistry().registerTrackedView(view); 30 | container.withLocalTx(() -> { 31 | store.update("toDelete2", new TestObject("A1")); 32 | store.update("2", new TestObject("A2")); 33 | store.remove("toDelete2"); 34 | store.update("3", new TestObject("B1")); 35 | store.update("4", new TestObject("B2")); 36 | store.remove("toDelete1"); 37 | }); 38 | container.withLocalTx(() -> { 39 | store.remove("toDelete2"); 40 | store.update("toDelete3", new TestObject("A1")); 41 | store.remove("toDelete3"); 42 | store.update("4", new TestObject("A4")); 43 | store.update("5", new TestObject("A3")); 44 | int viewVal = store.getTrackedViewRegistry().getView(TrackedTestView.class).getCount(); 45 | assertEquals(5, viewVal); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/cloning/JacisCloningObjectAdapterUnsafe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.cloning; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.plugin.readonly.DefaultJacisStoreEntryReadOnlyModeAdapter; 9 | import org.jacis.plugin.readonly.JacisStoreEntryReadOnlyModeAdapter; 10 | 11 | /** 12 | * Implementation of the {@link AbstractJacisCloningObjectAdapter} cloning the objects based on the Java clone method. 13 | * The behavior is similar to the {@link JacisCloningObjectAdapter} but returning a read only version of the object does not first check if the object supports a read only mode. 14 | * 15 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 16 | * @author Jan Wiemer 17 | */ 18 | @JacisApi 19 | @SuppressWarnings({"unused", "UnusedReturnValue"}) // since this is an API of the library 20 | public class JacisCloningObjectAdapterUnsafe extends JacisCloningObjectAdapter { 21 | 22 | /** 23 | * Create a cloning object adapter with the passed read only mode adapter. 24 | * 25 | * @param readOnlyModeAdapters Adapter to switch an object between read-only and read-write mode (if supported). 26 | */ 27 | public JacisCloningObjectAdapterUnsafe(JacisStoreEntryReadOnlyModeAdapter readOnlyModeAdapters) { 28 | super(readOnlyModeAdapters); 29 | if (readOnlyModeAdapters == null) { 30 | throw new IllegalArgumentException(this.getClass().getName() + " can only be initialized with a read only mode adapter!"); 31 | } 32 | } 33 | 34 | /** 35 | * Create a cloning object adapter with a default read only mode adapter (see {@link DefaultJacisStoreEntryReadOnlyModeAdapter}). 36 | */ 37 | public JacisCloningObjectAdapterUnsafe() { 38 | super(); 39 | } 40 | 41 | @Override 42 | public V cloneCommitted2ReadOnlyTxView(V value) { 43 | return value; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/persistence/microstream/MicrostreamStoreEntity.java: -------------------------------------------------------------------------------- 1 | package org.jacis.extension.persistence.microstream; 2 | 3 | /** 4 | * The Microstream entity object representing the store entries. 5 | * The entity objects build a linked list (see {@link #getPrev()} and {@link #getNext()}). 6 | * 7 | * @author Jan Wiemer 8 | * 9 | * @param Key type of the store entry 10 | * @param Value type of the store entry 11 | */ 12 | class MicrostreamStoreEntity { 13 | 14 | /** The key of the store entry represented by this Microstream entity object. */ 15 | private K key; 16 | /** The value of the store entry represented by this Microstream entity object. */ 17 | private V value; 18 | /** The next element in the linked list. */ 19 | private MicrostreamStoreEntity next; 20 | /** The previous element in the linked list. */ 21 | private MicrostreamStoreEntity prev; 22 | 23 | public MicrostreamStoreEntity(K key, V value) { 24 | this.key = key; 25 | this.value = value; 26 | } 27 | 28 | public K getKey() { 29 | return key; 30 | } 31 | 32 | public V getValue() { 33 | return value; 34 | } 35 | 36 | public MicrostreamStoreEntity getNext() { 37 | return next; 38 | } 39 | 40 | public MicrostreamStoreEntity getPrev() { 41 | return prev; 42 | } 43 | 44 | public MicrostreamStoreEntity setKey(K key) { 45 | this.key = key; 46 | return this; 47 | } 48 | 49 | public MicrostreamStoreEntity setValue(V value) { 50 | this.value = value; 51 | return this; 52 | } 53 | 54 | public MicrostreamStoreEntity setNext(MicrostreamStoreEntity next) { 55 | this.next = next; 56 | return this; 57 | } 58 | 59 | public MicrostreamStoreEntity setPrev(MicrostreamStoreEntity prev) { 60 | this.prev = prev; 61 | return this; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "Entity(" + key + ")"; 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/cloning/JacisCloneable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.cloning; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * This interface should be implemented by an object that should be cloned by the 11 | * {@link JacisCloningObjectAdapter} by simply calling the {@link #clone()} method. 12 | *

13 | * Note that an implementation of the clone method usually overwrites the {@link Object#clone()} method. 14 | * This means a clone can be initialized by calling 'super.clone()' with the semantic that all primitive members 15 | * and all references are already cloned by the super method. How deep referred objects (specially referred 16 | * container objects like collections) should be cloned has to be carefully designed. 17 | * The design of the stored object together with the implementation of the clone method has to guarantee 18 | * that no modification of the cloned object itself or referred (reachable) objects can modify the original 19 | * object or its referred (reachable) objects. That means an object that is suitable to be a JACIS cloneable object 20 | * must only contain properties that are: 21 | *

22 | * * primitive types, and therefore immutable; 23 | * * immutable types; 24 | * * other JACIS cloneable objects that are deeply cloned with the original object 25 | * * collections of other JACIS cloneable objects that are deeply cloned with the original object (the collection itself has to be cloned as well) 26 | *

27 | * It is also possible to simply overwrite the {@link Object#clone()} method without implementing this interface. 28 | * However, in this case the {@link JacisCloningObjectAdapter} has to call the clone method by reflection. 29 | * 30 | * @param The type of the object implementing this interface. 31 | * @author Jan Wiemer 32 | */ 33 | @JacisApi 34 | public interface JacisCloneable extends Cloneable { 35 | 36 | /** @return A clone of this object */ 37 | OT clone(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/readonly/object/JacisReadonlyModeSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.readonly.object; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.plugin.readonly.DefaultJacisStoreEntryReadOnlyModeAdapter; 9 | import org.jacis.plugin.readonly.JacisStoreEntryReadOnlyModeAdapter; 10 | 11 | /** 12 | * This interface can be implemented by objects that support switching between read-write and read-only mode. 13 | * The default implementation (class {@link DefaultJacisStoreEntryReadOnlyModeAdapter}) 14 | * for the {@link JacisStoreEntryReadOnlyModeAdapter} interface uses this interface to switch the read / write mode. 15 | *

16 | * Note that the implementation of the object is responsible to ensure that an object in read-only mode really prevents 17 | * all modifications, usually by throwing an exception (e.g. an {@link org.jacis.exception.ReadOnlyException}). 18 | *

19 | * An abstract base class simplifying to implement this interface is the {@link AbstractReadOnlyModeSupportingObject} class. 20 | * 21 | * @author Jan Wiemer 22 | */ 23 | @JacisApi 24 | public interface JacisReadonlyModeSupport { 25 | 26 | /** 27 | * Switches the object into the read-only mode 28 | */ 29 | void switchToReadOnlyMode(); 30 | 31 | /** 32 | * Switches the object into the read-write mode 33 | */ 34 | void switchToReadWriteMode(); 35 | 36 | /** 37 | * @return if the object is currently in the read-only mode 38 | */ 39 | boolean isReadOnly(); 40 | 41 | /** 42 | * @return if the object is writable for the current thread 43 | */ 44 | boolean isWritable(); 45 | 46 | /** 47 | * Switches the read / write mode depending on the passed parameter 48 | * 49 | * @param readOnlyMode if 'true' switch to read-only mode, if 'false' switch to read-write mode 50 | */ 51 | default void switchToReadOnlyMode(boolean readOnlyMode) { 52 | if (readOnlyMode) { 53 | switchToReadOnlyMode(); 54 | } else { 55 | switchToReadWriteMode(); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/serialization/JacisSerializationObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.serialization; 6 | 7 | import java.io.Serializable; 8 | 9 | import org.jacis.plugin.objectadapter.JacisObjectAdapter; 10 | 11 | /** 12 | * Abstract generic implementation of the {@link org.jacis.plugin.objectadapter.JacisObjectAdapter} copying the objects 13 | * to and from the transactional view by means of a serialization mechanism. 14 | * Serialization and de-serialization of the objects is delegated to the abstract methods 15 | * {@link #serialize(Serializable)} and {@link #deserialize(byte[])}. 16 | * 17 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 18 | * @author Jan Wiemer 19 | */ 20 | public abstract class JacisSerializationObjectAdapter implements JacisObjectAdapter { 21 | 22 | @Override 23 | public TV cloneCommitted2WritableTxView(byte[] bytes) { 24 | return deserialize(bytes); 25 | } 26 | 27 | @Override 28 | public TV cloneCommitted2ReadOnlyTxView(byte[] bytes) { 29 | return deserialize(bytes); 30 | } 31 | 32 | @Override 33 | public TV cloneTxView2ReadOnlyTxView(TV value) { 34 | return deserialize(serialize(value)); 35 | } 36 | 37 | @Override 38 | public byte[] cloneTxView2Committed(TV value) { 39 | return serialize(value); 40 | } 41 | 42 | /** 43 | * Serialize the passed object to a byte array. 44 | * 45 | * @param obj The object to serialize. 46 | * @return The bytes of the serialized object. 47 | */ 48 | protected abstract byte[] serialize(TV obj); 49 | 50 | /** 51 | * De-serialize an object from the passed byte array. 52 | * 53 | * @param bytes The bytes from which to de-serialize the object. 54 | * @return The de-serialized object. 55 | */ 56 | protected abstract TV deserialize(byte[] bytes); 57 | 58 | @Override 59 | public String toString() { 60 | return getClass().getSimpleName(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/store/JacisStoreAdminInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.store; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer; 9 | import org.jacis.container.JacisObjectTypeSpec; 10 | import org.jacis.plugin.objectadapter.JacisObjectAdapter; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Administration interface for a JACIS store. 16 | *

17 | * This interface provides methods to add or remove transaction listeners or maintain the registered tracked views. 18 | * 19 | * @param Key type of the store entry 20 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 21 | * @param Type of the objects as they are stored in the internal map of committed values. This type is not visible from the outside. 22 | * @author Jan Wiemer 23 | */ 24 | @JacisApi 25 | public interface JacisStoreAdminInterface { 26 | 27 | /** @return the store */ 28 | JacisStore getStore(); 29 | 30 | /** @return the store identifier uniquely identifying this store inside the container */ 31 | JacisContainer.StoreIdentifier getStoreIdentifier(); 32 | 33 | /** @return the object type specification for the objects stored in this store */ 34 | JacisObjectTypeSpec getObjectTypeSpec(); 35 | 36 | /** @return the object adapter defining how to copy objects from the committed view to a transactional view and back */ 37 | JacisObjectAdapter getObjectAdapter(); 38 | 39 | /** 40 | * Returns a info object /type {@link StoreEntryInfo}) containing information regarding the current state of the object 41 | * (regarding the committed values and the current transactional view). 42 | * 43 | * @param key The key of the desired object. 44 | * @return a info object /type {@link StoreEntryInfo}) containing information regarding the current state of the object. 45 | */ 46 | StoreEntryInfo getObjectInfo(K key); 47 | 48 | public List getAllIndexDefinitions(); 49 | 50 | public List getTransactionInfos(); 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/testhelper/TrackedTestView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.testhelper; 6 | 7 | import java.util.List; 8 | 9 | import org.jacis.trackedviews.TrackedView; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * Test tracked view... 15 | * 16 | * @author Jan Wiemer 17 | */ 18 | public class TrackedTestView implements TrackedView { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(TestObject.class); 21 | 22 | private int count = 0; 23 | private long sum = 0; 24 | 25 | @SuppressWarnings("unchecked") 26 | @Override 27 | public TrackedView clone() { 28 | try { 29 | return (TrackedView) super.clone(); 30 | } catch (CloneNotSupportedException e) { 31 | throw new RuntimeException("clone murks"); 32 | } 33 | } 34 | 35 | public int getCount() { 36 | return count; 37 | } 38 | 39 | public long getSum() { 40 | return sum; 41 | } 42 | 43 | @Override 44 | public void clear() { 45 | count = 0; 46 | sum = 0; 47 | } 48 | 49 | @Override 50 | public void trackModification(TestObject oldValue, TestObject newValue) { 51 | log.trace("VIEW: tack modification {} -> {} --> {}", oldValue, newValue, Thread.currentThread().getName()); 52 | sum += newValue == null ? 0 : newValue.getValue(); 53 | sum -= oldValue == null ? 0 : oldValue.getValue(); 54 | if (oldValue == null && newValue != null) { 55 | count++; 56 | } else if (oldValue != null && newValue == null) { 57 | count--; 58 | } 59 | } 60 | 61 | @Override 62 | public void checkView(List values) { 63 | if (count != values.stream().filter(v -> v != null).count()) { 64 | throw new RuntimeException("View expects " + count + " values but we have: " + values.size() + "! Values: " + values); 65 | } 66 | long checkSum = values.stream().mapToLong(v -> v == null ? 0 : v.getValue()).sum(); 67 | if (sum != checkSum) { 68 | throw new RuntimeException("View expects sum " + sum + " values but we have: " + checkSum + "! Values: " + values); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/store/TrackedViewTransactionLocal.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020. Jan Wiemer */ 2 | 3 | package org.jacis.store; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.jacis.trackedviews.TrackedView; 9 | 10 | /** 11 | * Wrapper class for a tracked view in order to keep track of the modifications done in the transaction in which the view snapshot has been taken. 12 | * This class is used by the JACIS store to track all modifications done 13 | *

    14 | *
  • inside this transaction up to the time when the snapshot is taken (in the method {@link JacisStoreTxView#getTrackedView(String, java.util.function.Supplier)})
  • 15 | *
  • inside this transaction after the snapshot is taken (in the method {@link JacisStoreTxView#updateValue(StoreEntryTxView, Object)})
  • 16 | *
17 | * Therefore if you need the view multiple times inside a transaction (with some modifications between the accesses), you do not need to clone the view again. 18 | * Note that each update will track the modification from the previous update to this update. 19 | * 20 | * @param Key type of the store entry 21 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 22 | */ 23 | class TrackedViewTransactionLocal { 24 | 25 | /** reference to the tracked view itself */ 26 | final private TrackedView trackedView; 27 | /** map containing all objects modified during this transaction (containing always the last updated value) */ 28 | final private Map lastUpdatedEntries = new HashMap<>(); 29 | 30 | TrackedViewTransactionLocal(TrackedView trackedView) { 31 | this.trackedView = trackedView; 32 | } 33 | 34 | void trackModification(TV origValue, TV value, StoreEntryTxView entry) { 35 | TV lastUpdatedValue = lastUpdatedEntries.get(entry.getKey()); 36 | trackedView.trackModification(lastUpdatedValue == null ? origValue : lastUpdatedValue, value); 37 | @SuppressWarnings("unchecked") 38 | TV clone = (TV) entry.getCommittedEntry().getStore().getObjectAdapter().cloneTxView2Committed(value); 39 | lastUpdatedEntries.put(entry.getKey(), clone); 40 | } 41 | 42 | TrackedView getTrackedView() { 43 | return trackedView; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/serialization/JacisJavaSerializationObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.serialization; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.ObjectInputStream; 11 | import java.io.ObjectOutputStream; 12 | import java.io.Serializable; 13 | 14 | /** 15 | * Generic implementation of the {@link org.jacis.plugin.objectadapter.JacisObjectAdapter} copying the objects 16 | * to and from the transactional view by means of Java serialization. 17 | * 18 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 19 | * @author Jan Wiemer 20 | */ 21 | public class JacisJavaSerializationObjectAdapter extends JacisSerializationObjectAdapter { 22 | 23 | @Override 24 | protected byte[] serialize(TV obj) { 25 | if (obj == null) { 26 | return null; 27 | } 28 | try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 29 | try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { 30 | oos.writeObject(obj); 31 | return bos.toByteArray(); 32 | } 33 | } catch (IOException e) { 34 | throw new RuntimeException("Serialization object to byte[] failed! Object: " + obj, e); 35 | } 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | @Override 40 | protected TV deserialize(byte[] bytes) { 41 | if (bytes == null) { 42 | return null; 43 | } 44 | try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) { 45 | try (ObjectInputStream ois = new ObjectInputStream(bis)) { 46 | return (TV) ois.readObject(); 47 | } 48 | } catch (IOException | ClassNotFoundException e) { 49 | throw new RuntimeException("Deserialization object from byte[] failed! Bytes: " + toHexStr(bytes), e); 50 | } 51 | } 52 | 53 | private String toHexStr(byte[] bytes) { 54 | StringBuilder sb = new StringBuilder(); 55 | for (byte b : bytes) { 56 | sb.append(String.format("%02X", b)); 57 | } 58 | return sb.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/readonly/JacisStoreEntryReadOnlyModeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.readonly; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * The read only mode adapter is used to define how objects are switched between the read-write and read-only mode (if supported). 11 | *

12 | * The default implementation (class {@link DefaultJacisStoreEntryReadOnlyModeAdapter}) is applicable for objects 13 | * implementing the {@link org.jacis.plugin.readonly.object.JacisReadonlyModeSupport} interface 14 | * and uses the methods declared in this interface for switching the mode. 15 | * 16 | * @param The type of the values that should be switched between read-write and read-only mode. 17 | * @author Jan Wiemer 18 | */ 19 | @JacisApi 20 | public interface JacisStoreEntryReadOnlyModeAdapter { 21 | 22 | /** 23 | * Returns if this adapter can switch the read / write mode for the passed object 24 | * 25 | * @param value The object to check. 26 | * @return if this adapter can switch the read / write mode for the passed object 27 | */ 28 | boolean isApplicableTo(V value); 29 | 30 | /** 31 | * Returns if the passed object is in read-only mode 32 | * 33 | * @param value The object to check. 34 | * @return if the passed object is in read-only mode 35 | */ 36 | boolean isReadOnly(V value); 37 | 38 | /** 39 | * Switch the read / write mode for the passed object to read-only mode 40 | * Note that the calling methods will work with the returned value. 41 | * Therefore the implementation of the method may return another instance than the passed (e.g. a proxy). 42 | * 43 | * @param value The object to switch to read-only mode 44 | * @return the object with the switched mode. 45 | */ 46 | V switchToReadOnlyMode(V value); 47 | 48 | /** 49 | * Switch the read / write mode for the passed object to read-write mode 50 | * Note that the calling methods will work with the returned value. 51 | * Therefore the implementation of the method may return another instance than the passed (e.g. a proxy). 52 | * 53 | * @param value The object to switch to read-write mode 54 | * @return the object with the switched mode. 55 | */ 56 | V switchToReadWriteMode(V value); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/objectadapter/cloning/JacisStoreWithCloningTrackedViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.objectadapter.cloning; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import org.jacis.container.JacisContainer; 10 | import org.jacis.store.JacisStore; 11 | import org.jacis.testhelper.JacisTestHelper; 12 | import org.jacis.testhelper.TestObject; 13 | import org.jacis.testhelper.TrackedTestView; 14 | import org.jacis.trackedviews.TrackedView; 15 | import org.junit.Test; 16 | 17 | public class JacisStoreWithCloningTrackedViewTest { 18 | 19 | @Test 20 | public void testTrackedView() { 21 | JacisTestHelper testHelper = new JacisTestHelper(); 22 | JacisStore store = testHelper.createTestStoreWithCloning(); 23 | JacisContainer container = store.getContainer(); 24 | container.withLocalTx(() -> { 25 | store.update("1", new TestObject("A1")); 26 | store.update("toDelete1", new TestObject("A1")); 27 | }); 28 | TrackedView view = new TrackedTestView(); 29 | store.getTrackedViewRegistry().registerTrackedView(view); 30 | container.withLocalTx(() -> { 31 | store.update("toDelete2", new TestObject("A1")); 32 | store.update("2", new TestObject("A2")); 33 | store.remove("toDelete2"); 34 | store.update("3", new TestObject("B1")); 35 | store.update("4", new TestObject("B2")); 36 | store.remove("toDelete1"); 37 | }); 38 | container.withLocalTx(() -> { 39 | store.remove("toDelete2"); 40 | store.update("toDelete3", new TestObject("A1")); 41 | store.remove("toDelete3"); 42 | store.update("4", new TestObject("A4")); 43 | store.update("5", new TestObject("A3")); 44 | int viewVal = store.getTrackedViewRegistry().getView(TrackedTestView.class).getCount(); 45 | assertEquals(5, viewVal); 46 | }); 47 | int viewVal = store.getTrackedViewRegistry().getView(TrackedTestView.class).getCount(); 48 | assertEquals(5, viewVal); 49 | store.getTrackedViewRegistry().reinitializeView(TrackedTestView.class); 50 | viewVal = store.getTrackedViewRegistry().getView(TrackedTestView.class).getCount(); 51 | assertEquals(5, viewVal); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/index/AbstractJacisMultiIndex.java: -------------------------------------------------------------------------------- 1 | package org.jacis.index; 2 | 3 | import org.jacis.JacisApi; 4 | 5 | import java.util.Set; 6 | import java.util.function.Function; 7 | 8 | /** 9 | * Abstract base class for Jacis Indices where one value object can have multiple index keys. 10 | * 11 | * @param Index key type for this unique index 12 | * @param Key type of the store entry 13 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 14 | * @author Jan Wiemer 15 | */ 16 | @JacisApi 17 | public abstract class AbstractJacisMultiIndex { 18 | 19 | /** Name of the index used to register it at the store. The index names have to be unique for one store. */ 20 | protected final String indexName; 21 | /** Reference to the index registry storing all indices registered for a store. */ 22 | protected final JacisIndexRegistry indexRegistry; 23 | /** Function defining how to compute the set of index keys from the value stored in the store. */ 24 | protected final Function> indexKeyFunction; 25 | 26 | AbstractJacisMultiIndex(String indexName, Function> indexKeyFunction, JacisIndexRegistry indexRegistry) { 27 | this.indexName = indexName; 28 | this.indexKeyFunction = indexKeyFunction; 29 | this.indexRegistry = indexRegistry; 30 | } 31 | 32 | /** @return the name of the index (the name used to register the index during creation). */ 33 | public String getIndexName() { 34 | return indexName; 35 | } 36 | 37 | /** @return The function to compute the set of the index keys from the stored value. */ 38 | Function> getIndexKeyFunction() { 39 | return indexKeyFunction; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return getClass().getSimpleName() + "(" + indexName + ")"; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return indexName.hashCode(); 50 | } 51 | 52 | @SuppressWarnings("unchecked") 53 | @Override 54 | public boolean equals(Object that) { 55 | if (that == null) { 56 | return false; 57 | } 58 | if (!this.getClass().equals(that.getClass())) { 59 | return false; 60 | } 61 | return this.indexName.equals(((AbstractJacisMultiIndex) that).indexName); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/util/ConcurrentHashSet.java: -------------------------------------------------------------------------------- 1 | package org.jacis.util; 2 | 3 | 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class ConcurrentHashSet implements Set { 11 | private final Map map; 12 | private static final Object OBJ = new Object(); 13 | 14 | public ConcurrentHashSet(int size) { 15 | this.map = new ConcurrentHashMap<>(size); 16 | } 17 | 18 | public ConcurrentHashSet() { 19 | this.map = new ConcurrentHashMap(); 20 | } 21 | 22 | public int size() { 23 | return this.map.size(); 24 | } 25 | 26 | public boolean isEmpty() { 27 | return this.map.isEmpty(); 28 | } 29 | 30 | public boolean contains(Object o) { 31 | return this.map.containsKey(o); 32 | } 33 | 34 | public Iterator iterator() { 35 | return this.map.keySet().iterator(); 36 | } 37 | 38 | public Object[] toArray() { 39 | return this.map.keySet().toArray(); 40 | } 41 | 42 | public T[] toArray(T[] a) { 43 | return this.map.keySet().toArray(a); 44 | } 45 | 46 | public boolean add(E e) { 47 | return this.map.put(e, OBJ) == null; 48 | } 49 | 50 | public boolean remove(Object o) { 51 | return this.map.remove(o) != null; 52 | } 53 | 54 | public boolean containsAll(Collection c) { 55 | return this.map.keySet().containsAll(c); 56 | } 57 | 58 | public boolean addAll(Collection collection) { 59 | boolean changed = false; 60 | Iterator iter = collection.iterator(); 61 | while (iter.hasNext()) { 62 | E e = iter.next(); 63 | if (this.map.put(e, OBJ) == null) { 64 | changed = true; 65 | } 66 | } 67 | return changed; 68 | } 69 | 70 | public boolean retainAll(Collection collection) { 71 | throw new UnsupportedOperationException(); 72 | } 73 | 74 | public boolean removeAll(Collection collection) { 75 | boolean changed = false; 76 | Iterator iter = collection.iterator(); 77 | while (iter.hasNext()) { 78 | Object e = iter.next(); 79 | if (this.map.remove(e) != null) { 80 | changed = true; 81 | } 82 | } 83 | return changed; 84 | } 85 | 86 | public void clear() { 87 | this.map.clear(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/testhelper/TestObjectWithoutReadOnlyMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.testhelper; 6 | 7 | import java.io.Serializable; 8 | 9 | import org.jacis.plugin.objectadapter.cloning.JacisCloneable; 10 | 11 | /** 12 | * A JACIS cloneable test object that does *not* provide a read only mode. 13 | * 14 | * @author Jan Wiemer 15 | */ 16 | public class TestObjectWithoutReadOnlyMode implements JacisCloneable, Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | private String name; 21 | private long value; 22 | 23 | public TestObjectWithoutReadOnlyMode(String name, long value) { 24 | this.name = name; 25 | this.value = value; 26 | } 27 | 28 | @Override 29 | public TestObjectWithoutReadOnlyMode clone() { 30 | try { 31 | return (TestObjectWithoutReadOnlyMode) super.clone(); 32 | } catch (CloneNotSupportedException e) { 33 | throw new InternalError("Could not clone " + this.getClass().getName()); 34 | } 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public TestObjectWithoutReadOnlyMode setName(String name) { 42 | this.name = name; 43 | return this; 44 | } 45 | 46 | public long getValue() { 47 | return value; 48 | } 49 | 50 | public TestObjectWithoutReadOnlyMode setValue(long value) { 51 | this.value = value; 52 | return this; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return getClass().getSimpleName() + "(" + name + ":" + value + ")"; 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | final int prime = 31; 63 | int result = 1; 64 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 65 | result = prime * result + (int) (value ^ (value >>> 32)); 66 | return result; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object obj) { 71 | if (this == obj) 72 | return true; 73 | if (obj == null) 74 | return false; 75 | if (getClass() != obj.getClass()) 76 | return false; 77 | TestObjectWithoutReadOnlyMode other = (TestObjectWithoutReadOnlyMode) obj; 78 | if (name == null) { 79 | if (other.name != null) 80 | return false; 81 | } else if (!name.equals(other.name)) 82 | return false; 83 | return value == other.value; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/JacisContainerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertNotNull; 9 | 10 | import org.jacis.container.JacisContainer; 11 | import org.jacis.container.JacisObjectTypeSpec; 12 | import org.jacis.plugin.objectadapter.cloning.JacisCloningObjectAdapter; 13 | import org.jacis.store.JacisStore; 14 | import org.jacis.store.JacisStoreAdminInterface; 15 | import org.jacis.testhelper.TestObject; 16 | import org.junit.Test; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | public class JacisContainerTest { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(JacisContainerTest.class); 23 | 24 | @Test 25 | public void testCreateContainer() { 26 | JacisContainer container = new JacisContainer(); 27 | assertNotNull(container); 28 | log.info("The JacisContainer: {}", container); 29 | } 30 | 31 | @Test 32 | public void testRegisterStore() { 33 | JacisContainer container = new JacisContainer(); 34 | JacisObjectTypeSpec objectTypeSpec = new JacisObjectTypeSpec<>(String.class, TestObject.class, new JacisCloningObjectAdapter<>()); 35 | objectTypeSpec.setCheckViewsOnCommit(true); 36 | objectTypeSpec.setTrackOriginalValue(true); 37 | container.createStore(objectTypeSpec); 38 | assertEquals(1, container.getAllStores().size()); 39 | JacisStoreAdminInterface store = container.getStoreAdminInterface(String.class, TestObject.class); 40 | assertEquals(String.class, store.getObjectTypeSpec().getKeyClass()); 41 | assertEquals(TestObject.class, store.getObjectTypeSpec().getValueClass()); 42 | assertEquals(String.class, store.getObjectTypeSpec().getKeyClass()); 43 | assertEquals(TestObject.class, store.getObjectTypeSpec().getValueClass()); 44 | assertNotNull(store); 45 | } 46 | 47 | @Test 48 | public void testClearStores() { 49 | JacisContainer container = new JacisContainer(); 50 | JacisObjectTypeSpec objectTypeSpec = new JacisObjectTypeSpec<>(String.class, TestObject.class, new JacisCloningObjectAdapter<>()); 51 | container.createStore(objectTypeSpec); 52 | JacisStore store = container.getStore(String.class, TestObject.class); 53 | container.clearAllStores(); 54 | assertNotNull(store); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/txadapter/JacisTransactionAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.txadapter; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer; 9 | import org.jacis.container.JacisTransactionHandle; 10 | 11 | import java.util.Collection; 12 | 13 | /** 14 | * Transaction adapter that can be registered to bind the Jacis Store to externally managed transactions. 15 | *

16 | * This interface declares the methods required by the Jacis Store to interact with externally managed transactions. 17 | * Basically the Jacis store 18 | * 19 | *

    20 | *
  • has to check if there is currently a transaction active,
  • 21 | *
  • let the container join the currently active transaction,
  • 22 | *
  • remove the association between the container and the external transaction (when the transaction is finished).
  • 23 | *
24 | * 25 | * @author Jan Wiemer 26 | */ 27 | @JacisApi 28 | public interface JacisTransactionAdapter { 29 | 30 | /** 31 | * @return If there is currently an active external transaction. 32 | */ 33 | boolean isTransactionActive(); 34 | 35 | /** 36 | * Let the container (and therefore all its stores) join the currently active external transaction. 37 | * Inside the store the transaction is represented by the returned transaction handle. 38 | * If there is no external transaction the method returns null. 39 | * 40 | * @param container The container (type {@link JacisContainer}) that shall join the external transaction 41 | * @return The transaction handle (type {@link JacisTransactionHandle}) representing the external transaction inside the jacis store. 42 | */ 43 | JacisTransactionHandle joinCurrentTransaction(JacisContainer container); 44 | 45 | /** 46 | * Remove the association between the container and the external transaction (when the transaction is finished). 47 | */ 48 | @SuppressWarnings("SpellCheckingInspection") 49 | void disjoinCurrentTransaction(JacisTransactionHandle transaction); 50 | 51 | /** 52 | * @param externalTransaction The external (global or local transaction) 53 | * @return the JACIS transaction handle associated with the external transaction 54 | */ 55 | JacisTransactionHandle getTransactionHandle(Object externalTransaction); 56 | 57 | /** 58 | * @return all JACIS transaction handles for all active transactions 59 | */ 60 | Collection getAllTransactionHandles(); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisTrackedViewModificationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer.StoreIdentifier; 9 | import org.jacis.container.JacisTransactionHandle; 10 | import org.jacis.store.JacisStore; 11 | import org.jacis.trackedviews.TrackedView; 12 | 13 | /** 14 | * Exception thrown in case the modification of a tracked view during commit causes an exception. 15 | * 16 | * @author Jan Wiemer 17 | */ 18 | @JacisApi 19 | public class JacisTrackedViewModificationException extends RuntimeException { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | private final StoreIdentifier storeIdentifier; 24 | private final TrackedView view; 25 | private final String txId; 26 | private final String txDescription; 27 | private final Object key; 28 | private final Object oldValue; 29 | private final Object newValue; 30 | 31 | public JacisTrackedViewModificationException(JacisStore store, TrackedView view, JacisTransactionHandle transaction, Object key, Object oldValue, Object newValue, Exception e) { 32 | super(computeMessage(store, view, transaction, key, oldValue, newValue, e), e); 33 | this.view = view; 34 | this.key = key; 35 | this.oldValue = oldValue; 36 | this.newValue = newValue; 37 | storeIdentifier = store.getStoreIdentifier(); 38 | txId = transaction.getTxId(); 39 | txDescription = transaction.getTxDescription(); 40 | } 41 | 42 | private static String computeMessage(JacisStore store, TrackedView view, JacisTransactionHandle transaction, Object key, Object oldValue, Object newValue, Exception e) { 43 | return "Tracking modification for TX " + transaction.getTxId() + " on view " + view + " of store " + store.getStoreIdentifier() + " causes exception: >" + e.toString() + "" + key + "< from >" + oldValue + "< to >" + newValue + "<)"; 45 | } 46 | 47 | public StoreIdentifier getStoreIdentifier() { 48 | return storeIdentifier; 49 | } 50 | 51 | public TrackedView getView() { 52 | return view; 53 | } 54 | 55 | public String getTxId() { 56 | return txId; 57 | } 58 | 59 | public String getTxDescription() { 60 | return txDescription; 61 | } 62 | 63 | public Object getKey() { 64 | return key; 65 | } 66 | 67 | public Object getOldValue() { 68 | return oldValue; 69 | } 70 | 71 | public Object getNewValue() { 72 | return newValue; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '38 5 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'java' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/persistence/xodus/XodusPersistenceAdapter.java: -------------------------------------------------------------------------------- 1 | package org.jacis.extension.persistence.xodus; 2 | 3 | import org.jacis.store.KeyValuePair; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | import jetbrains.exodus.ArrayByteIterable; 9 | import jetbrains.exodus.ByteIterable; 10 | import jetbrains.exodus.bindings.StringBinding; 11 | import jetbrains.exodus.env.Environment; 12 | 13 | /** 14 | * Implementation of the JACIS persistence adapter based on JetBrains Xodus 15 | * (see https://github.com/JetBrains/xodus) 16 | * using JSON to serialize the JACIS objects. 17 | * 18 | * @param Key type of the store entry 19 | * @param Value type of the store entry 20 | * 21 | * @author Jan Wiemer 22 | */ 23 | public class XodusPersistenceAdapter extends AbstractXodusPersistenceAdapter { 24 | 25 | /** The JSON object mapper used to convert the objects and keys in the JACIS store to Strings. */ 26 | private final ObjectMapper objectMapper; 27 | 28 | public XodusPersistenceAdapter(Environment env, ObjectMapper objectMapper, boolean traceLogging) { 29 | super(env, traceLogging); 30 | this.objectMapper = objectMapper; 31 | } 32 | 33 | public XodusPersistenceAdapter(Environment env, ObjectMapper objectMapper) { 34 | this(env, objectMapper, false); 35 | } 36 | 37 | @Override 38 | protected KeyValuePair encode(K key, V value) { 39 | try { 40 | String keyString = objectMapper.writeValueAsString(key); 41 | String valString = value == null ? null : objectMapper.writeValueAsString(value); 42 | ArrayByteIterable xodusKey = StringBinding.stringToEntry(keyString); 43 | ArrayByteIterable xodusVal = valString == null ? null : StringBinding.stringToEntry(valString); 44 | return new KeyValuePair<>(xodusKey, xodusVal); 45 | } catch (JsonProcessingException e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | 50 | @Override 51 | @SuppressWarnings("unchecked") 52 | protected KeyValuePair decode(ByteIterable xodusKey, ByteIterable xodusValue) { 53 | try { 54 | String keyString = StringBinding.entryToString(xodusKey); 55 | String valString = StringBinding.entryToString(xodusValue); 56 | K key = (K) objectMapper.readValue(keyString, storeIdentifier.getKeyClass()); 57 | V val = (V) objectMapper.readValue(valString, storeIdentifier.getValueClass()); 58 | return new KeyValuePair<>(key, val); 59 | } catch (JsonProcessingException e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/exception/JacisModificationListenerException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.exception; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer.StoreIdentifier; 9 | import org.jacis.container.JacisTransactionHandle; 10 | import org.jacis.plugin.JacisModificationListener; 11 | import org.jacis.store.JacisStore; 12 | 13 | /** 14 | * Exception thrown in case the modification of a tracked view during commit causes an exception. 15 | * 16 | * @author Jan Wiemer 17 | */ 18 | @SuppressWarnings({"unused", "UnusedReturnValue"}) // since this is an API of the library 19 | @JacisApi 20 | public class JacisModificationListenerException extends RuntimeException { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private final StoreIdentifier storeIdentifier; 25 | private final JacisModificationListener listener; 26 | private final String txId; 27 | private final String txDescription; 28 | private final Object key; 29 | private final Object oldValue; 30 | private final Object newValue; 31 | 32 | public JacisModificationListenerException(JacisStore store, JacisModificationListener listener, JacisTransactionHandle transaction, Object key, Object oldValue, Object newValue, Exception e) { 33 | super(computeMessage(store, listener, transaction, key, oldValue, newValue, e), e); 34 | this.listener = listener; 35 | this.key = key; 36 | this.oldValue = oldValue; 37 | this.newValue = newValue; 38 | storeIdentifier = store.getStoreIdentifier(); 39 | txId = transaction.getTxId(); 40 | txDescription = transaction.getTxDescription(); 41 | } 42 | 43 | private static String computeMessage(JacisStore store, JacisModificationListener listener, JacisTransactionHandle transaction, Object key, Object oldValue, Object newValue, Exception e) { 44 | return "Tracking modification for TX " + transaction.getTxId() + " on listener " + listener + " of store " + store.getStoreIdentifier() + " causes exception: >" + e.toString() + "" + key + "< from >" + oldValue + "< to >" + newValue + "<)"; 46 | } 47 | 48 | public StoreIdentifier getStoreIdentifier() { 49 | return storeIdentifier; 50 | } 51 | 52 | public JacisModificationListener getListener() { 53 | return listener; 54 | } 55 | 56 | public String getTxId() { 57 | return txId; 58 | } 59 | 60 | public String getTxDescription() { 61 | return txDescription; 62 | } 63 | 64 | public Object getKey() { 65 | return key; 66 | } 67 | 68 | public Object getOldValue() { 69 | return oldValue; 70 | } 71 | 72 | public Object getNewValue() { 73 | return newValue; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/performance/JacisPerformanceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.performance; 6 | 7 | import org.jacis.store.JacisStore; 8 | import org.jacis.testhelper.JacisTestHelper; 9 | import org.jacis.testhelper.TestObject; 10 | import org.junit.Test; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | public class JacisPerformanceTest { 15 | 16 | private static final Logger log = LoggerFactory.getLogger(JacisPerformanceTest.class); 17 | 18 | @Test 19 | public void testInsertPerformanceSerialization() { 20 | JacisStore store = new JacisTestHelper().createTestStoreWithSerialization(); 21 | long mem0 = getUsedMem(); 22 | long t0 = System.nanoTime(); 23 | int nTx = 10000; 24 | int nIns = 10; 25 | doInserts(store, nTx, nIns); 26 | long duration = System.nanoTime() - t0; 27 | long mem = getUsedMem() - mem0; 28 | log.info("Jacis Store based on Serialization {} TX a {} inserts: {} ms {} bytes", nTx, nIns, milliStr(duration), memStr(mem)); 29 | } 30 | 31 | @Test 32 | public void testInsertPerformanceCloning() { 33 | JacisStore store = new JacisTestHelper().createTestStoreWithCloning(); 34 | long mem0 = getUsedMem(); 35 | long t0 = System.nanoTime(); 36 | int nTx = 10000; 37 | int nIns = 10; 38 | doInserts(store, nTx, nIns); 39 | long duration = System.nanoTime() - t0; 40 | long mem = getUsedMem() - mem0; 41 | log.info("Jacis Store based on cloning {} TX a {} inserts: {} ms {} bytes", nTx, nIns, milliStr(duration), memStr(mem)); 42 | } 43 | 44 | private void doInserts(JacisStore store, int nTx, int sizeTx) { 45 | for (int i = 0; i < nTx; i++) { 46 | int txIdx = i; 47 | store.getContainer().withLocalTx(() -> { 48 | for (int j = 0; j < sizeTx; j++) { 49 | TestObject testObject = new TestObject("obj-" + txIdx + "-" + j, txIdx + j); 50 | store.update(testObject.getName(), testObject); 51 | } 52 | }); 53 | } 54 | } 55 | 56 | private long getUsedMem() { 57 | for (int i = 0; i < 10; i++) { 58 | Runtime.getRuntime().gc(); 59 | } 60 | return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 61 | } 62 | 63 | private String milliStr(long nanos) { 64 | return String.format("%.2f", ((double) nanos) / (double) (1000 * 1000 * 1000)); 65 | } 66 | 67 | private String memStr(long bytes) { 68 | boolean si = true; 69 | int unit = si ? 1000 : 1024; 70 | if (bytes < unit) { 71 | return bytes + " B"; 72 | } 73 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 74 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 75 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/persistence/microstream/MicrostreamStoreRoot.java: -------------------------------------------------------------------------------- 1 | package org.jacis.extension.persistence.microstream; 2 | 3 | import org.jacis.container.JacisContainer; 4 | import org.jacis.container.JacisContainer.StoreIdentifier; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** 11 | * The root object stored by the Microstream storage manager. 12 | * Basically it stores (the head of) the linked list of Microstream entity objects representing the store entries. 13 | * 14 | * @param Key type of the store entry 15 | * @param Value type of the store entry 16 | * @author Jan Wiemer 17 | */ 18 | class MicrostreamStoreRoot { 19 | 20 | /** The identifier of the JACIS store represented by this root object. */ 21 | private final JacisContainer.StoreIdentifier storeIdentifier; 22 | /** The first object of the linked list of Microstream entity objects representing the store entries. */ 23 | private MicrostreamStoreEntity firstElement; 24 | 25 | public MicrostreamStoreRoot(JacisContainer.StoreIdentifier storeIdentifier) { 26 | this.storeIdentifier = storeIdentifier; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | String b = "StorageRoot(" + storeIdentifier.toShortString() + ")"; 32 | return b; 33 | } 34 | 35 | public void add(MicrostreamStoreEntity entity, Set objectsToStore) { 36 | if (firstElement != null) { 37 | firstElement.setPrev(entity); 38 | entity.setNext(firstElement); 39 | } 40 | firstElement = entity; 41 | objectsToStore.add(entity); 42 | objectsToStore.add(this); 43 | } 44 | 45 | public void remove(MicrostreamStoreEntity entity, Set objectsToStore) { 46 | MicrostreamStoreEntity prev = entity.getPrev(); 47 | MicrostreamStoreEntity next = entity.getNext(); 48 | if (prev != null) { 49 | prev.setNext(next); 50 | objectsToStore.add(prev); 51 | } else { 52 | objectsToStore.add(this); 53 | firstElement = next; 54 | } 55 | if (next != null) { 56 | next.setPrev(prev); 57 | objectsToStore.add(next); 58 | } 59 | entity.setPrev(null).setNext(null); 60 | objectsToStore.add(entity); 61 | } 62 | 63 | public MicrostreamStoreEntity getFirstElement() { 64 | return firstElement; 65 | } 66 | 67 | public void setFirstElement(MicrostreamStoreEntity firstElement) { 68 | this.firstElement = firstElement; 69 | } 70 | 71 | public StoreIdentifier getStoreIdentifier() { 72 | return storeIdentifier; 73 | } 74 | 75 | public List> toList() { 76 | List> res = new ArrayList<>(); 77 | MicrostreamStoreEntity e = firstElement; 78 | while (e != null) { 79 | res.add(e); 80 | e = e.getNext(); 81 | } 82 | return res; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/cloning/JacisCloningObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.cloning; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | 10 | import org.jacis.JacisApi; 11 | import org.jacis.plugin.readonly.DefaultJacisStoreEntryReadOnlyModeAdapter; 12 | import org.jacis.plugin.readonly.JacisStoreEntryReadOnlyModeAdapter; 13 | 14 | /** 15 | * Implementation of the {@link AbstractJacisCloningObjectAdapter} cloning the objects based on Java serialization. 16 | * If the object is an instance of the {@link JacisCloneable} interface the {@link JacisCloneable#clone()} method 17 | * declared in this interface is used to clone the object. 18 | * Otherwise, the object may be cloneable (overwrites the {@link Object#clone()} methods) 19 | * but does not implement the {@link JacisCloneable} interface. 20 | * In this case the {@link Object#clone()} method is called by reflection. 21 | * 22 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 23 | * @author Jan Wiemer 24 | */ 25 | @JacisApi 26 | public class JacisCloningObjectAdapter extends AbstractJacisCloningObjectAdapter { 27 | 28 | /** 29 | * Create a cloning object adapter with the passed read only mode adapter. 30 | * 31 | * @param readOnlyModeAdapters Adapter to switch an object between read-only and read-write mode (if supported). 32 | */ 33 | public JacisCloningObjectAdapter(JacisStoreEntryReadOnlyModeAdapter readOnlyModeAdapters) { 34 | super(readOnlyModeAdapters); 35 | } 36 | 37 | /** 38 | * Create a cloning object adapter with a default read only mode adapter (see {@link DefaultJacisStoreEntryReadOnlyModeAdapter}). 39 | */ 40 | public JacisCloningObjectAdapter() { 41 | super(); 42 | } 43 | 44 | @SuppressWarnings("unchecked") 45 | @Override 46 | protected V cloneValue(V value) { 47 | if (value == null) { 48 | return null; 49 | } 50 | V clone; 51 | if (value instanceof JacisCloneable) { 52 | clone = ((JacisCloneable) value).clone(); 53 | } else { 54 | clone = cloneByReflection(value); 55 | } 56 | return clone; 57 | } 58 | 59 | @SuppressWarnings("unchecked") 60 | private V cloneByReflection(V obj) { 61 | try { 62 | Method cloneMethod = obj.getClass().getDeclaredMethod("clone"); 63 | return (V) cloneMethod.invoke(obj); 64 | } catch (NoSuchMethodException e) { 65 | throw new IllegalArgumentException("Failed to clone object " + obj + "! No clone method declared: " + e, e); 66 | } catch (SecurityException | IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { 67 | throw new IllegalArgumentException("Failed to clone object " + obj + "! Clone method not accessible: " + e, e); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/txadapter/local/JacisTransactionAdapterLocal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.txadapter.local; 6 | 7 | import org.jacis.container.JacisContainer; 8 | import org.jacis.container.JacisTransactionHandle; 9 | import org.jacis.exception.JacisTransactionAlreadyStartedException; 10 | import org.jacis.plugin.txadapter.JacisTransactionAdapter; 11 | 12 | import java.util.Collection; 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.atomic.AtomicLong; 16 | 17 | /** 18 | * Default implementation of the transaction adapter using local transactions. 19 | * 20 | * @author Jan Wiemer 21 | */ 22 | public class JacisTransactionAdapterLocal implements JacisTransactionAdapter { 23 | 24 | /** Thread local to store the currently active transaction handle for the current thread. */ 25 | protected final ThreadLocal transaction = new ThreadLocal<>(); 26 | /** Map storing the transaction handles for the active local transactions. */ 27 | protected final Map txMap = new ConcurrentHashMap<>(); 28 | /** Sequence to give the started local transactions a unique id */ 29 | private final AtomicLong txSeq = new AtomicLong(0); 30 | 31 | @Override 32 | public String toString() { 33 | return getClass().getSimpleName(); 34 | } 35 | 36 | @Override 37 | public JacisTransactionHandle getTransactionHandle(Object externalTransaction) { 38 | return txMap.get(externalTransaction); 39 | } 40 | 41 | @Override 42 | public Collection getAllTransactionHandles() { 43 | return txMap.values(); 44 | } 45 | 46 | @Override 47 | public boolean isTransactionActive() { 48 | return transaction.get() != null; 49 | } 50 | 51 | @Override 52 | public JacisTransactionHandle joinCurrentTransaction(JacisContainer container) { 53 | return transaction.get();// no special operation to join needed for locally managed transactions 54 | } 55 | 56 | @Override 57 | public void disjoinCurrentTransaction(JacisTransactionHandle activeTx) { 58 | JacisTransactionHandle tx = this.transaction.get(); 59 | if (tx != null) { 60 | txMap.remove(tx.getExternalTransaction()); 61 | } 62 | this.transaction.remove(); 63 | } 64 | 65 | public JacisLocalTransaction startLocalTransaction(JacisContainer jacisContainer, String txDescription) { 66 | JacisTransactionHandle tx = transaction.get(); 67 | if (tx != null) { 68 | throw new JacisTransactionAlreadyStartedException("Transaction already started: " + tx); 69 | } 70 | long txNr = txSeq.incrementAndGet(); 71 | String txId = "TX-" + txNr; 72 | JacisLocalTransaction localJacisTx = new JacisLocalTransaction(txId); 73 | tx = new JacisTransactionHandle(txId, txDescription, localJacisTx); 74 | transaction.set(tx); 75 | localJacisTx.associateWithJacisTransaction(tx, jacisContainer); 76 | txMap.put(localJacisTx, tx); 77 | return localJacisTx; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/modificationlistener/JacisModificationListenerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.modificationlistener; 6 | 7 | import org.jacis.container.JacisTransactionHandle; 8 | import org.jacis.exception.JacisModificationVetoException; 9 | import org.jacis.plugin.JacisModificationListener; 10 | import org.jacis.store.JacisStore; 11 | import org.jacis.testhelper.JacisTestHelper; 12 | import org.jacis.testhelper.TestObject; 13 | import org.junit.Test; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | @SuppressWarnings("CodeBlock2Expr") 18 | public class JacisModificationListenerTest { 19 | 20 | static class TestModificationListener implements JacisModificationListener { 21 | @Override 22 | public void onAdjustBeforePrepare(String key, TestObject oldValue, TestObject valueToCommit, JacisTransactionHandle tx) { 23 | if (oldValue != null) { 24 | assertTrue(oldValue.isReadOnly()); 25 | } 26 | assertFalse(valueToCommit.isReadOnly()); 27 | if (valueToCommit != null) { 28 | if ("A".equals(valueToCommit.getStrValue())) { 29 | valueToCommit.setValue(oldValue.getValue() + 1); 30 | } 31 | if (oldValue != null && "A".equals(oldValue.getStrValue())) { 32 | valueToCommit.setValue(oldValue.getValue() - 1); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void onPrepareModification(String key, TestObject oldValue, TestObject newValue, JacisTransactionHandle tx) throws JacisModificationVetoException { 39 | if (oldValue != null) { 40 | assertTrue(oldValue.isReadOnly()); 41 | } 42 | if (newValue != null) { 43 | assertTrue(newValue.isReadOnly()); 44 | } 45 | } 46 | 47 | @Override 48 | public void onModification(String key, TestObject oldValue, TestObject newValue, JacisTransactionHandle tx) { 49 | if (oldValue != null) { 50 | assertTrue(oldValue.isReadOnly()); 51 | } 52 | if (newValue != null) { 53 | assertTrue(newValue.isReadOnly()); 54 | } 55 | // do nothing 56 | } 57 | } 58 | 59 | @Test 60 | public void testGetCommittedVersion() { 61 | String objName = "OBJ"; 62 | JacisTestHelper testHelper = new JacisTestHelper(); 63 | JacisStore store = testHelper.createTestStoreWithCloning(); 64 | store.registerModificationListener(new TestModificationListener()); 65 | store.getContainer().withLocalTx(() -> store.update(objName, new TestObject(objName, 0))); 66 | assertEquals(0, store.getCommittedValue(objName).getValue()); 67 | // -------------------- 68 | store.getContainer().withLocalTx(() -> store.update(objName, new TestObject(objName, 0).setStrValue("A"))); 69 | assertEquals(1, store.getCommittedValue(objName).getValue()); 70 | // -------------------- 71 | store.getContainer().withLocalTx(() -> store.update(objName, new TestObject(objName, 0).setStrValue("B"))); 72 | assertEquals(0, store.getCommittedValue(objName).getValue()); 73 | // -------------------- 74 | } 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/dirtycheck/JacisDirtyCheck.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.dirtycheck; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.store.JacisStoreImpl; 9 | 10 | /** 11 | * This interface provides the possibility to register a dirty check for an object type. 12 | * The purpose of this check is to mark the object as updated without the need to explicitly call the 13 | * {@link JacisStoreImpl#update(Object, Object)} method. If this method returns 'true' the 14 | * object is marked as updated. Note that it is still possible to mark an object as update manually by 15 | * calling the 'update' method. For objects already marked as updated the dirty check is not called. 16 | *

17 | * Note that the original value passed to the check method can only be used to implement a dirty check 18 | * if the original value is tracked by the store 19 | * (see org.jacis.container.JacisObjectTypeSpec#trackOriginalValue)). 20 | * Otherwise always 'null' will be passed to the original value parameter. 21 | *

22 | * Note that the implementation of the method may be based on comparing the properties of the value with 23 | * the properties of the original value (the classical dirty check), or by setting a dirty flag at the object 24 | * all the time a modifying method is called. The latter can be combined with the 25 | * {@link org.jacis.plugin.readonly.object.AbstractReadOnlyModeSupportingObject} implementation of the 26 | * {@link org.jacis.plugin.readonly.object.JacisReadonlyModeSupport} since objects derived from this class 27 | * anyway have to call a AbstractReadOnlyModeSupportingObject#checkWritable() method in each method 28 | * modifying the object. 29 | * 30 | * @param Key type of the store entry 31 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 32 | * @author Jan Wiemer 33 | */ 34 | @JacisApi 35 | @SuppressWarnings({"unused", "UnusedReturnValue"}) // since this is an API of the library 36 | public interface JacisDirtyCheck { 37 | 38 | /** 39 | * Checks if the passed object is dirty. 40 | * The purpose of this check is to mark the object as updated without the need to explicitly call the 41 | * {@link JacisStoreImpl#update(Object, Object)} method. If this method returns 'true' the 42 | * object is marked as updated. Note that it is still possible to mark an object as update manually by 43 | * calling the 'update' method. For objects already marked as updated the dirty check is not called. 44 | * 45 | * @param key The key of the object to check. 46 | * @param originalValue The original value (when the object is cloned to the transactional view; 47 | * if the original value is tracked otherwise null 48 | * (see org.jacis.container.JacisObjectTypeSpec#trackOriginalValue)). 49 | * @param currentValue The current value that should be checked if it is dirty. 50 | * @return if the object is dirty, that is the current value has changed compared to the original value. 51 | */ 52 | boolean isDirty(K key, TV originalValue, TV currentValue); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/trackedviews/TrackedView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.trackedviews; 6 | 7 | import java.util.List; 8 | 9 | import org.jacis.JacisApi; 10 | import org.jacis.plugin.objectadapter.cloning.JacisCloneable; 11 | import org.jacis.store.JacisStoreImpl; 12 | 13 | /** 14 | * A tracked view can be registered at a JACIS store to automatically keep a view (typically some accumulated values) up to date. 15 | * The store keeps the registered instance and tracks all committed changes at this instance. 16 | * Accessing a tracked view always causes the JACIS store to create a snapshot (a cloned instance) of the view maintained in the store (only reflecting committed modifications) 17 | * On access the modifications pending in the current TX are tracked on the snapshot. 18 | * Modifications done in afterwards in the same transaction as getting the tracked view are tracked as well on the snapshot. 19 | *

20 | * Caution: a tracked view must not modify the objects passed to the method tracking the modifications! 21 | * 22 | * @param The type of the original values (from the store) 23 | * @author Jan Wiemer 24 | */ 25 | @JacisApi 26 | public interface TrackedView extends JacisCloneable> { 27 | 28 | /** 29 | * Track modification of the passed object. 30 | * The method is called during commit for each modified object. 31 | * The method gets the old value (the original value at the point of time 32 | * the value was first cloned to the transactional view of the currently committed transaction) 33 | * and the new value that is now committed to the store. 34 | * 35 | * @param oldValue the value was first cloned to the transactional 36 | * @param newValue the new value that is now committed to the store 37 | */ 38 | void trackModification(V oldValue, V newValue); 39 | 40 | /** 41 | * The implementation of this method should check the consistency of the tracked view. 42 | * The method is called after executing a commit is finished. 43 | * Note that the method is only called if this is specified in the specification of the store 44 | * (see org.jacis.container.JacisObjectTypeSpec#checkViewsOnCommit 45 | * (default is false)). 46 | * The method gets all committed values as parameter. 47 | * Usually the implementation goes through all these values and calculates the expected values of 48 | * the tracked view on these values. If these values differ from the actually tracked values the 49 | * method should throw an exception. 50 | * 51 | * @param values all committed values stored in the store (after the just finished commit) 52 | */ 53 | void checkView(List values); 54 | 55 | /** 56 | * Clear the tracked view. 57 | * After executing this method the view should reflect the state where no committed values are stored. 58 | * The method is only called by the {@link JacisStoreImpl#clear()} method. 59 | */ 60 | void clear(); 61 | 62 | /** 63 | * @return if the implementation of the view is thread safe. Default is false. Overwrite this method to declare a view to be thread safe. 64 | */ 65 | default boolean isThreadSafe() { 66 | return false; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/index/AbstractJacisIndex.java: -------------------------------------------------------------------------------- 1 | package org.jacis.index; 2 | 3 | import org.jacis.JacisApi; 4 | 5 | import java.util.Optional; 6 | import java.util.function.Function; 7 | 8 | /** 9 | * Abstract base class for Jacis Indices. 10 | * 11 | * @param Index key type for this unique index 12 | * @param Key type of the store entry 13 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 14 | * @author Jan Wiemer 15 | */ 16 | @JacisApi 17 | public abstract class AbstractJacisIndex { 18 | 19 | /** Name of the index used to register it at the store. The index names have to be unique for one store. */ 20 | protected final String indexName; 21 | /** Reference to the index registry storing all indices registered for a store. */ 22 | protected final JacisIndexRegistry indexRegistry; 23 | /** Function defining how to compute the index key from the value stored in the store. */ 24 | protected final Function indexKeyFunction; 25 | /** Flag indicating if the values mapping to an index key of null should also be tracked. */ 26 | protected final boolean trackNullIndexKeys; 27 | 28 | AbstractJacisIndex(String indexName, Function indexKeyFunction, JacisIndexRegistry indexRegistry, boolean trackNullIndexKeys) { 29 | this.indexName = indexName; 30 | this.indexKeyFunction = indexKeyFunction; 31 | this.indexRegistry = indexRegistry; 32 | this.trackNullIndexKeys = trackNullIndexKeys; 33 | } 34 | 35 | AbstractJacisIndex(String indexName, Function indexKeyFunction, JacisIndexRegistry indexRegistry) { 36 | this(indexName, indexKeyFunction, indexRegistry, true); 37 | } 38 | 39 | /** @return the name of the index (the name used to register the index during creation). */ 40 | public String getIndexName() { 41 | return indexName; 42 | } 43 | 44 | protected boolean isTrackNullIndexKey() { 45 | return trackNullIndexKeys; 46 | } 47 | 48 | protected Object wrapIndexKey(Object key) { 49 | return key != null ? key : isTrackNullIndexKey() ? NULL_REPLACEMENT : null; 50 | } 51 | 52 | private static final Object NULL_REPLACEMENT = new Object() { 53 | @Override 54 | public String toString() { 55 | return "NULL_KEY"; 56 | } 57 | }; 58 | 59 | /** @return The function to compute an index key from the stored value. */ 60 | @SuppressWarnings("unchecked") 61 | Function getIndexKeyFunction() { 62 | if (isTrackNullIndexKey()) { 63 | return v -> Optional.ofNullable(indexKeyFunction.apply(v)).orElse(NULL_REPLACEMENT); 64 | } else { 65 | return (Function) indexKeyFunction; 66 | } 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return getClass().getSimpleName() + "(" + indexName + ")"; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return indexName.hashCode(); 77 | } 78 | 79 | @SuppressWarnings("unchecked") 80 | @Override 81 | public boolean equals(Object that) { 82 | if (that == null) { 83 | return false; 84 | } 85 | if (!this.getClass().equals(that.getClass())) { 86 | return false; 87 | } 88 | return this.indexName.equals(((AbstractJacisIndex) that).indexName); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/testhelper/TestObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.testhelper; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import org.jacis.plugin.dirtycheck.object.AbstractReadOnlyModeAndDirtyCheckSupportingObject; 9 | import org.jacis.plugin.objectadapter.cloning.JacisCloneable; 10 | 11 | import java.io.Serializable; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | /** 17 | * A JACIS cloneable test object that also provides a read only mode. 18 | * 19 | * @author Jan Wiemer 20 | */ 21 | @JsonIgnoreProperties({"readOnly", "writable"}) 22 | public class TestObject extends AbstractReadOnlyModeAndDirtyCheckSupportingObject implements JacisCloneable, Serializable { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | private String name; 27 | private long value; 28 | private String strValue; 29 | 30 | TestObject() { 31 | super(); 32 | } 33 | 34 | public TestObject(String name, long value) { 35 | this.name = name; 36 | this.value = value; 37 | strValue = null; 38 | } 39 | 40 | public TestObject(String name) { 41 | this(name, 1); 42 | } 43 | 44 | @Override 45 | public TestObject clone() { 46 | return (TestObject) super.clone(); 47 | } 48 | 49 | public String getName() { 50 | return name; 51 | } 52 | 53 | public TestObject setName(String name) { 54 | checkWritable(); 55 | this.name = name; 56 | return this; 57 | } 58 | 59 | public long getValue() { 60 | return value; 61 | } 62 | 63 | public TestObject setValue(long value) { 64 | checkWritable(); 65 | this.value = value; 66 | return this; 67 | } 68 | 69 | public String getStrValue() { 70 | return strValue; 71 | } 72 | 73 | public TestObject setStrValue(String strValue) { 74 | this.strValue = strValue; 75 | return this; 76 | } 77 | 78 | public Set getAllStrValue() { 79 | return Stream.of(strValue, name).filter(s -> s != null).collect(Collectors.toSet()); 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | StringBuilder b = new StringBuilder(); 85 | b.append(getClass().getSimpleName()); 86 | b.append("(").append(name).append(":").append(value); 87 | if (strValue != null) { 88 | b.append(", strVal=").append(strValue); 89 | } 90 | b.append(")"); 91 | return b.toString(); 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | final int prime = 31; 97 | int result = 1; 98 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 99 | result = prime * result + (int) (value ^ (value >>> 32)); 100 | return result; 101 | } 102 | 103 | @Override 104 | public boolean equals(Object obj) { 105 | if (this == obj) 106 | return true; 107 | if (obj == null) 108 | return false; 109 | if (getClass() != obj.getClass()) 110 | return false; 111 | TestObject other = (TestObject) obj; 112 | if (name == null) { 113 | if (other.name != null) 114 | return false; 115 | } else if (!name.equals(other.name)) 116 | return false; 117 | return value == other.value; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/objectadapter/cloning/microstream/MicrostreamObjectCopier.java: -------------------------------------------------------------------------------- 1 | package org.jacis.extension.objectadapter.cloning.microstream; 2 | 3 | import org.eclipse.serializer.collections.types.XGettingCollection; 4 | import org.eclipse.serializer.persistence.binary.types.Binary; 5 | import org.eclipse.serializer.persistence.binary.types.BinaryPersistence; 6 | import org.eclipse.serializer.persistence.binary.types.BinaryPersistenceFoundation; 7 | import org.eclipse.serializer.persistence.exceptions.PersistenceExceptionTransfer; 8 | import org.eclipse.serializer.persistence.types.*; 9 | import org.eclipse.serializer.reference.Reference; 10 | import org.eclipse.serializer.util.X; 11 | 12 | import java.io.Closeable; 13 | 14 | import static org.eclipse.serializer.util.X.notNull; 15 | 16 | 17 | interface MicrostreamObjectCopier extends Closeable { 18 | 19 | T copy(T source); 20 | 21 | @Override 22 | void close(); 23 | 24 | static MicrostreamObjectCopier New() { 25 | return new Default(BinaryPersistence.Foundation()); 26 | } 27 | 28 | static MicrostreamObjectCopier New(final BinaryPersistenceFoundation foundation) { 29 | return new Default(notNull(foundation)); 30 | } 31 | 32 | class Default implements MicrostreamObjectCopier { 33 | private final BinaryPersistenceFoundation foundation; 34 | private PersistenceManager persistenceManager; 35 | 36 | Default(final BinaryPersistenceFoundation foundation) { 37 | this.foundation = foundation; 38 | } 39 | 40 | @SuppressWarnings("unchecked") 41 | @Override 42 | public synchronized T copy(final T source) { 43 | this.lazyInit(); 44 | 45 | this.persistenceManager.store(source); 46 | return (T) this.persistenceManager.get(); 47 | } 48 | 49 | @Override 50 | public synchronized void close() { 51 | if (this.persistenceManager != null) { 52 | this.persistenceManager.objectRegistry().clearAll(); 53 | this.persistenceManager.close(); 54 | this.persistenceManager = null; 55 | } 56 | } 57 | 58 | private void lazyInit() { 59 | if (this.persistenceManager == null) { 60 | final Reference buffer = X.Reference(null); 61 | final CopySource source = () -> X.Constant(buffer.get()); 62 | final CopyTarget target = buffer::set; 63 | 64 | final BinaryPersistenceFoundation foundation = this.foundation 65 | .setPersistenceSource(source) 66 | .setPersistenceTarget(target) 67 | .setContextDispatcher(PersistenceContextDispatcher.LocalObjectRegistration()); 68 | 69 | foundation.setTypeDictionaryManager( 70 | PersistenceTypeDictionaryManager.Transient( 71 | foundation.getTypeDictionaryCreator())); 72 | 73 | this.persistenceManager = foundation.createPersistenceManager(); 74 | } else { 75 | this.persistenceManager.objectRegistry().truncateAll(); 76 | } 77 | } 78 | 79 | interface CopySource extends PersistenceSource { 80 | @SuppressWarnings("SpellCheckingInspection") 81 | @Override 82 | default XGettingCollection readByObjectIds(final PersistenceIdSet[] oids) 83 | throws PersistenceExceptionTransfer { 84 | return null; 85 | } 86 | } 87 | 88 | interface CopyTarget extends PersistenceTarget { 89 | @Override 90 | default boolean isWritable() { 91 | return true; 92 | } 93 | } 94 | 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/persistence/JacisPersistenceAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.persistence; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisTransactionHandle; 9 | import org.jacis.plugin.JacisModificationListener; 10 | import org.jacis.plugin.JacisTransactionListener; 11 | import org.jacis.store.JacisStore; 12 | 13 | /** 14 | * Interface for persistence adapters that may be plugged into a JACIS store to make it a durable store. 15 | *

16 | * The interface extends the {@link JacisModificationListener} interface to enable the implementation to 17 | * keep track of all changes to the objects in the store. 18 | * Furthermore, the persistence adapter is notified when the transaction demarcation methods have been executed for a store. 19 | * Another method is called on startup to initialize the store with the data already stored persistently. 20 | * 21 | * @param Key type of the store entry 22 | * @param Value type of the store entry 23 | * @author Jan Wiemer 24 | */ 25 | @JacisApi 26 | @SuppressWarnings({"unused", "UnusedReturnValue"}) // since this is an API of the library 27 | public interface JacisPersistenceAdapter extends JacisModificationListener, JacisTransactionListener { 28 | 29 | /** 30 | * Called on startup to initialize the passed JACIS store with the already persistently stored data. 31 | * 32 | * @param store The JACIS store to initialize 33 | */ 34 | void initializeStore(JacisStore store); 35 | 36 | /** 37 | * Called after the prepare phase of the transaction has been executed for the store. 38 | *

39 | * The persistence adapter may check if the storing during commit will probably succeed without exceptions. 40 | * The goal is that all exceptions should be thrown in the prepare phase and the commit phase succeeds without exceptions. 41 | * 42 | * @param store The store for which prepare has been executed. 43 | * @param tx The transaction for which prepare has been executed. 44 | */ 45 | default void afterPrepareForStore(JacisStore store, JacisTransactionHandle tx) { 46 | // default: do nothing 47 | } 48 | 49 | /** 50 | * Called after the commit phase of the transaction has been executed for the store. 51 | *

52 | * During the commit phase all modifications on the objects in the store are tracked at the 53 | * persistence adapter using the {@link JacisModificationListener#onModification(Object, Object, Object, org.jacis.container.JacisTransactionHandle)} method. 54 | * The persistence adapter may collect the modifications and flush them together to the persistent store after commit in this method. 55 | * 56 | * @param store The store for which prepare has been executed. 57 | * @param tx The transaction for which prepare has been executed. 58 | */ 59 | default void afterCommitForStore(JacisStore store, JacisTransactionHandle tx) { 60 | // default: do nothing 61 | } 62 | 63 | /** 64 | * Called after the rollback phase of the transaction has been executed for the store. 65 | *

66 | * If the transaction is rolled back the persistence adapter has to forget all modifications tracked during the transaction. 67 | * 68 | * @param store The store for which prepare has been executed. 69 | * @param tx The transaction for which prepare has been executed. 70 | */ 71 | default void afterRollbackForStore(JacisStore store, JacisTransactionHandle tx) { 72 | // default: do nothing 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/container/JacisTransactionHandle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.container; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.store.EventsJfr; 9 | 10 | import java.util.Objects; 11 | 12 | /** 13 | * Jacis handle for an external transaction. 14 | *

15 | * This class is used by the Jacis store to reference an external transaction the Jacis container is bound to. 16 | * 17 | * @author Jan Wiemer 18 | */ 19 | @JacisApi 20 | public class JacisTransactionHandle { 21 | 22 | /** The id of the transaction */ 23 | private final String txId; 24 | /** Description for the transaction giving some more information about the purpose of the transaction (for logging and debugging) */ 25 | private final String txDescription; 26 | /** A reference to the external (global) transaction (e.g. a JTA transaction) */ 27 | private final Object externalTransaction; 28 | /** Creation timestamp in milliseconds (System.currentTimeMillis()) */ 29 | private final long creationTimestampMs; 30 | /** JFR event to monitor the transaction in Java flight recorder. */ 31 | private final EventsJfr.JacisContainerTxJfrEvent jfrEvent; 32 | 33 | /** 34 | * Creates a transaction handle with the passed parameters. 35 | * 36 | * @param txId The id of the transaction 37 | * @param txDescription A description for the transaction giving some more information about the purpose of the transaction (for logging and debugging) 38 | * @param externalTransaction A reference to the external (global) transaction (e.g. a JTA transaction) this handle represents 39 | */ 40 | public JacisTransactionHandle(String txId, String txDescription, Object externalTransaction) { 41 | this.txId = txId; 42 | this.txDescription = txDescription; 43 | this.externalTransaction = externalTransaction; 44 | this.creationTimestampMs = System.currentTimeMillis(); 45 | jfrEvent = new EventsJfr.JacisContainerTxJfrEvent(txId, txDescription); 46 | jfrEvent.begin(); 47 | } 48 | 49 | /** @return The id of the transaction */ 50 | public String getTxId() { 51 | return txId; 52 | } 53 | 54 | /** @return A description for the transaction giving some more information about the purpose of the transaction (for logging and debugging) */ 55 | public String getTxDescription() { 56 | return txDescription; 57 | } 58 | 59 | /** @return A reference to the external (global) transaction (e.g. a JTA transaction) this handle represents */ 60 | public Object getExternalTransaction() { 61 | return externalTransaction; 62 | } 63 | 64 | /** @return Creation timestamp in milliseconds (System.currentTimeMillis()) */ 65 | public long getCreationTimestampMs() { 66 | return creationTimestampMs; 67 | } 68 | 69 | 70 | public EventsJfr.JacisContainerTxJfrEvent getJfrEvent() { 71 | return jfrEvent; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return externalTransaction.hashCode(); 77 | } 78 | 79 | @Override 80 | public boolean equals(Object obj) { 81 | if (this == obj) { 82 | return true; 83 | } else if (obj == null) { 84 | return false; 85 | } else if (getClass() != obj.getClass()) { 86 | return false; 87 | } 88 | JacisTransactionHandle that = (JacisTransactionHandle) obj; 89 | return Objects.equals(externalTransaction, that.externalTransaction); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "TX(" + txId + ": " + txDescription + ")"; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/objectadapter/serialization/JacisStoreWithSerializationMultiThreadedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.objectadapter.serialization; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import org.jacis.exception.JacisStaleObjectException; 12 | import org.jacis.store.JacisStore; 13 | import org.jacis.testhelper.JacisTestHelper; 14 | import org.jacis.testhelper.TestObject; 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | public class JacisStoreWithSerializationMultiThreadedTest { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(JacisStoreWithSerializationMultiThreadedTest.class); 22 | 23 | @Test() 24 | public void testSimpleMultiThreadedAccess() { 25 | for (int iteration = 0; iteration < 10; iteration++) { 26 | checkSimpleMultiThreadedAccess(); 27 | } 28 | } 29 | 30 | private void checkSimpleMultiThreadedAccess() { 31 | JacisStore store = new JacisTestHelper().createTestStoreWithSerialization(); 32 | String testObjName = "TST"; 33 | store.getContainer().withLocalTx(() -> store.update(testObjName, new TestObject(testObjName, 0).setStrValue("0"))); 34 | int numberOfThreads = 5; 35 | Thread[] threads = new Thread[numberOfThreads]; 36 | for (int i = 0; i < numberOfThreads; i++) { 37 | final int id = i; 38 | threads[i] = new Thread("TestThread-" + i) { 39 | @Override 40 | public void run() { 41 | try { 42 | AtomicBoolean finished = new AtomicBoolean(false); 43 | while (!finished.get()) { 44 | try { 45 | store.getContainer().withLocalTx(() -> { 46 | TestObject obj = store.get(testObjName); 47 | log.debug("{} reads {}", Thread.currentThread().getName(), obj); 48 | if (obj.getStrValue().endsWith("" + id)) { 49 | obj.setStrValue(obj.getStrValue() + (id + 1)); 50 | log.info("{} found its trigger value -> new content: {} ", Thread.currentThread().getName(), obj); 51 | store.update(testObjName, obj); 52 | finished.set(true); 53 | } 54 | }); 55 | Thread.sleep(10); 56 | } catch (JacisStaleObjectException e) { 57 | log.info("caught stale object exception: {}", e, e); 58 | } 59 | } 60 | } catch (InterruptedException e) { 61 | log.info("interrupted thread {}: {}", Thread.currentThread().getName(), e, e); 62 | Thread.currentThread().interrupt(); 63 | } catch (Throwable t) { 64 | log.error("Unexpected Exception {}", t, t); 65 | } finally { 66 | log.debug("terminate {}", Thread.currentThread().getName()); 67 | } 68 | } 69 | }; 70 | } 71 | for (int i = 0; i < numberOfThreads; i++) { 72 | threads[i].start(); 73 | } 74 | try { 75 | Thread.sleep(20); 76 | } catch (InterruptedException e) { 77 | log.info("sleep interrupted {}", e, e); 78 | } 79 | for (int i = 0; i < numberOfThreads; i++) { 80 | try { 81 | threads[i].join(); 82 | } catch (InterruptedException e) { 83 | log.info("join interrupted for thread {}: {]", threads[i].getName(), e); 84 | } 85 | } 86 | store.getContainer().withLocalTx(() -> { 87 | TestObject obj = store.get(testObjName); 88 | log.info("Result:" + obj); 89 | assertEquals("012345", obj.getStrValue()); 90 | }); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/objectadapter/cloning/JacisStoreWithCloningMultithreadedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.objectadapter.cloning; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import org.jacis.exception.JacisStaleObjectException; 12 | import org.jacis.store.JacisStore; 13 | import org.jacis.testhelper.JacisTestHelper; 14 | import org.jacis.testhelper.TestObject; 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | public class JacisStoreWithCloningMultithreadedTest { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(JacisStoreWithCloningMultithreadedTest.class); 22 | 23 | @Test() 24 | public void testSimpleMultiThreadedAccess() { 25 | for (int iter = 0; iter < 10; iter++) { 26 | checkSimpleMultiThreadedAccess(); 27 | } 28 | } 29 | 30 | public void checkSimpleMultiThreadedAccess() { 31 | JacisStore store = new JacisTestHelper().createTestStoreWithCloning(); 32 | String testObjName = "TST"; 33 | store.getContainer().withLocalTx(() -> { 34 | store.update(testObjName, new TestObject(testObjName, 0).setStrValue("0")); 35 | }); 36 | int numberOfThreads = 5; 37 | Thread[] threads = new Thread[numberOfThreads]; 38 | for (int i = 0; i < numberOfThreads; i++) { 39 | final int id = i; 40 | threads[i] = new Thread("TestThread-" + i) { 41 | @Override 42 | public void run() { 43 | try { 44 | AtomicBoolean finished = new AtomicBoolean(false); 45 | while (!finished.get()) { 46 | try { 47 | store.getContainer().withLocalTx(() -> { 48 | TestObject obj = store.get(testObjName); 49 | log.debug("{} reads {}", Thread.currentThread().getName(), obj); 50 | if (obj.getStrValue().endsWith("" + id)) { 51 | obj.setStrValue(obj.getStrValue() + (id + 1)); 52 | log.info("{} found its trigger value -> new content: {} ", Thread.currentThread().getName(), obj); 53 | store.update(testObjName, obj); 54 | finished.set(true); 55 | } 56 | }); 57 | Thread.sleep(10); 58 | } catch (JacisStaleObjectException e) { 59 | log.info("caught stale object exception: {}", e, e); 60 | } 61 | } 62 | } catch (InterruptedException e) { 63 | log.info("interupted thread {}: {}", Thread.currentThread().getName(), e, e); 64 | Thread.currentThread().interrupt(); 65 | } catch (Throwable t) { 66 | log.error("Unexpected Exception {}", t, t); 67 | } finally { 68 | log.debug("terminate {}", Thread.currentThread().getName()); 69 | } 70 | } 71 | }; 72 | } 73 | for (int i = 0; i < numberOfThreads; i++) { 74 | threads[i].start(); 75 | } 76 | try { 77 | Thread.sleep(20); 78 | } catch (InterruptedException e) { 79 | log.info("sleep interrupted {}", e, e); 80 | } 81 | for (int i = 0; i < numberOfThreads; i++) { 82 | try { 83 | threads[i].join(); 84 | } catch (InterruptedException e) { 85 | log.info("join interrupted for thread {}: {]", threads[i].getName(), e); 86 | } 87 | } 88 | store.getContainer().withLocalTx(() -> { 89 | TestObject obj = store.get(testObjName); 90 | log.info("Result:" + obj); 91 | assertEquals("012345", obj.getStrValue()); 92 | }); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/readonly/object/AbstractReadOnlyModeSupportingObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.readonly.object; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.exception.ReadOnlyException; 9 | 10 | /** 11 | * Abstract base class for objects supporting switching them between the usual read-write mode and a read-only mode. 12 | * Therefore, it implements the methods {@link #switchToReadOnlyMode()} and {@link #switchToReadWriteMode()} from 13 | * the {@link JacisReadonlyModeSupport} interface. Note that all the time only one single thread is allowed to 14 | * have write access to the object. When switching an object to read-write mode the current thread is stored 15 | * as thread with write access (see {@link #threadWithWriteAccess}). 16 | *

17 | * For actual implementations the class provides the protected method {@link #checkWritable()}. 18 | * This method should be called prior to all modifying accesses to the object (e.g. in all setter-methods). 19 | * The method will throw a {@link ReadOnlyException} if the current thread has no write access to the object. 20 | * 21 | * @author Jan Wiemer 22 | */ 23 | @JacisApi 24 | public abstract class AbstractReadOnlyModeSupportingObject implements JacisReadonlyModeSupport { 25 | 26 | public static final boolean WRITE_ACCESS_MODE_SINGLE_THREAD = true; 27 | 28 | /** The thread currently permitted to modify the object (if any) */ 29 | private transient Thread threadWithWriteAccess = null; 30 | 31 | protected AbstractReadOnlyModeSupportingObject() { 32 | threadWithWriteAccess = Thread.currentThread(); // when creating the object its writable 33 | } 34 | 35 | @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") 36 | @Override 37 | protected Object clone() { 38 | try { 39 | return super.clone(); 40 | } catch (CloneNotSupportedException e) { 41 | throw new InternalError("Could not clone " + this.getClass().getName()); 42 | } 43 | } 44 | 45 | @Override 46 | public void switchToReadOnlyMode() { 47 | threadWithWriteAccess = null; 48 | } 49 | 50 | @Override 51 | public void switchToReadWriteMode() { 52 | threadWithWriteAccess = Thread.currentThread(); 53 | } 54 | 55 | @Override 56 | public boolean isReadOnly() { 57 | return threadWithWriteAccess == null; 58 | } 59 | 60 | @Override 61 | public boolean isWritable() { 62 | if (isSingleThreadedWriteAccessRequired()) { 63 | return Thread.currentThread().equals(threadWithWriteAccess); 64 | } else { 65 | return threadWithWriteAccess != null; 66 | } 67 | } 68 | 69 | /** 70 | * This method should be called prior to all modifying accesses to the object (e.g. in al setter-methods). 71 | * The method will check if the current thread has write access to the object and will throw a {@link ReadOnlyException} otherwise. 72 | * 73 | * @throws ReadOnlyException thrown if the current thread has no write access to the object. 74 | */ 75 | protected void checkWritable() throws ReadOnlyException { 76 | if (threadWithWriteAccess == null) { 77 | throw new ReadOnlyException("Object currently in read only mode! Accessing Thread: " + Thread.currentThread() + ". Object: " + this); 78 | } else if (isSingleThreadedWriteAccessRequired() && !threadWithWriteAccess.equals(Thread.currentThread())) { 79 | throw new ReadOnlyException("Object currently only writable for thread " + threadWithWriteAccess + "! Accessing Thread: " + Thread.currentThread() + ". Object: " + this); 80 | } 81 | } 82 | 83 | /** 84 | * Overwrite this method and return false if write access should not be restricted to a single thread. 85 | * 86 | * @return if single threaded write access is required (default: true) 87 | */ 88 | protected boolean isSingleThreadedWriteAccessRequired() { 89 | return WRITE_ACCESS_MODE_SINGLE_THREAD; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/JacisObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter; 6 | 7 | import org.jacis.JacisApi; 8 | 9 | /** 10 | * The object adapter defines how to copy the objects to a transactional view and back. 11 | *

12 | * This interface for an object adapter provides the methods how to transfer objects 13 | * from the store of committed objects to the transaction view and back. 14 | * The object adapter can be defined individually for each store, that is each object type. 15 | * The actual implementation is stored in the {@link org.jacis.container.JacisObjectTypeSpec} for a store 16 | * (see org.jacis.container.JacisObjectTypeSpec#objectAdapter). 17 | *

18 | * The type parameters of this interface refer the type / class of the committed objects and the type / class 19 | * of the objects in the transactional view. The latter is the type the objects have for the outside world. 20 | * Depending on the implementation of the object adapter both types can be the same or different 21 | * (if e.g. the stored values are stored in a somehow compressed form). 22 | *

23 | * There are two generic default implementations for an object that can be used: 24 | * * One default implementation copies the objects by cloning them using the Java 'clone' method 25 | * (see {@link org.jacis.plugin.objectadapter.cloning.JacisCloningObjectAdapter}). 26 | * * The other default implementation copies the objects by Java serialization 27 | * (see {@link org.jacis.plugin.objectadapter.serialization.JacisJavaSerializationObjectAdapter}). 28 | * 29 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 30 | * @param Type of the objects as they are stored in the internal map of committed values. This type is not visible from the outside. 31 | * @author Jan Wiemer 32 | */ 33 | @JacisApi 34 | public interface JacisObjectAdapter { 35 | 36 | /** 37 | * Clone a committed version of the object into the transactional view. 38 | * Note that modifications ib the returned transactional view must not influence the committed version of the object. 39 | * 40 | * @param value Committed version of the object. 41 | * @return A transactional view for the passed object. 42 | */ 43 | TV cloneCommitted2WritableTxView(CV value); 44 | 45 | /** 46 | * Clone back a transactional view of the object into the committed version of the object. 47 | * The returned committed version will replace the previous one in the store of the committed values. 48 | * 49 | * @param value A transactional view for the passed object. 50 | * @return Committed version of the object. 51 | */ 52 | CV cloneTxView2Committed(TV value); 53 | 54 | /** 55 | * Clone a committed version of the object into a read only transactional view. 56 | * Modifications of the read only view must not be possible (lead to an exception). 57 | * As an optimization it is allowed to skip a real cloning here since the returned object is guaranteed to be read only. 58 | * Note that a difference using read only views where cloning is skipped is that a repeated read of the same object 59 | * may return different versions of the object if another transaction committed a newer version between both reads. 60 | * 61 | * @param value Committed version of the object. 62 | * @return A read only transactional view for the passed object. 63 | */ 64 | TV cloneCommitted2ReadOnlyTxView(CV value); 65 | 66 | /** 67 | * Convert a transactional view of an object into a read only representation of this object. 68 | * Modifications of the read only view must not be possible (lead to an exception). 69 | * 70 | * @param value A transactional view for the passed object. 71 | * @return A read only transactional view for the passed object. 72 | */ 73 | TV cloneTxView2ReadOnlyTxView(TV value); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/buildDeploy.yml: -------------------------------------------------------------------------------- 1 | # JACIS Continuous Integration GITHUB workflow 2 | name: JACIS-CI-Build 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | workflow_dispatch: 10 | inputs: 11 | release: 12 | description: 'Release Build' 13 | default: false 14 | 15 | jobs: 16 | build-and-test: 17 | name: Build and Test JACIS 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ windows-latest, ubuntu-latest, macos-latest ] 22 | java: [ 11, 17, 21 ] 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | - name: Fix Permissions 28 | run: chmod +x gradlew 29 | - name: Setup Java ${{ matrix.java }} 30 | uses: actions/setup-java@v3 31 | with: 32 | java-version: ${{ matrix.java }} 33 | distribution: 'temurin' 34 | - name: Test 35 | uses: gradle/gradle-build-action@v2 36 | with: 37 | arguments: build --scan -Dreleasebuild=${{ github.event.inputs.release }} 38 | 39 | deploy: 40 | name: Deploy JACIS 41 | needs: build-and-test 42 | runs-on: windows-latest 43 | steps: 44 | - name: Check if this is a Release Build 45 | if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true' }} 46 | run: echo "THIS IS A RELEASE BUILD!" 47 | - name: Checkout 48 | uses: actions/checkout@v3 49 | - name: Setup Java 17 50 | uses: actions/setup-java@v3 51 | with: 52 | java-version: 17 53 | distribution: 'temurin' 54 | - name: Deploy ( to https://maven.pkg.github.com/janwiemer/jacis ) 55 | uses: gradle/gradle-build-action@v2 56 | with: 57 | arguments: publish --scan -Dreleasebuild=${{ github.event.inputs.release }} -Dusedateversion=true -DdeployUser=JanWiemer -DdeployPw=${{secrets.GITHUB_TOKEN}} -DdeployRepoRelease=https://maven.pkg.github.com/janwiemer/jacis -DdeployRepoSnapshot=https://maven.pkg.github.com/janwiemer/jacis 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | - name: Determine Release versions 61 | if: ${{ github.event.inputs.release }} == true 62 | uses: HardNorth/github-version-generate@v1.3.0 63 | with: 64 | version-source: file 65 | version-file: gradle.properties 66 | version-file-extraction-pattern: '(?<=version=).+' 67 | - name: Get current time 68 | uses: josStorer/get-current-time@v2 69 | id: current-time 70 | with: 71 | format: YYYYMMDD 72 | - name: Log Release Version 73 | if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true' }} 74 | run: echo "version=${{ env.RELEASE_VERSION }}" 75 | - name: Log Snapshot Version 76 | if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.release != 'true' }} 77 | run: echo "version=${{ env.RELEASE_VERSION }}-build${{ steps.current-time.outputs.formattedTime }}-SNAPSHOT" 78 | - name: Create Release 79 | if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true' }} 80 | uses: actions/create-release@v1 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | with: 84 | tag_name: "v${{ env.RELEASE_VERSION }}" 85 | release_name: Release ${{ env.RELEASE_VERSION }} 86 | body: | 87 | To use this JACIS version add the following dependency to your project: 88 | 89 | 90 | org.jacis 91 | jacis 92 | ${{ env.RELEASE_VERSION }} 93 | 94 | 95 | For Gradle the dependency looks like: 96 | 97 | dependencies { 98 | implementation group: 'org.jacis', name: 'jacis', version: '${{ env.RELEASE_VERSION }}' 99 | } 100 | 101 | draft: false 102 | prerelease: false 103 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/JacisModificationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisObjectTypeSpec; 9 | import org.jacis.container.JacisTransactionHandle; 10 | import org.jacis.exception.JacisModificationVetoException; 11 | import org.jacis.store.JacisStoreImpl; 12 | 13 | /** 14 | * Listener that gets notified on each modification during commit. 15 | *

16 | * A listener implementing this interface can be registered at a transactional store 17 | * by passing it to the method {@link JacisStoreImpl#registerModificationListener(JacisModificationListener)}. 18 | * Once registered the method {@link #onModification(Object, Object, Object, JacisTransactionHandle)} of the listener is called 19 | * for each modification on the committed values in the store. The callback method is called during the commit phase 20 | * of the transaction when the modified values in the transactional view are written back to the store. 21 | * Note that a modification listener can only be registered for a store if this is configured to track the 22 | * original values of a transaction (see {@link JacisObjectTypeSpec#isTrackOriginalValueEnabled()}). 23 | * 24 | * @param Key type of the store entry 25 | * @param Value type of the store entry 26 | * @author Jan Wiemer 27 | */ 28 | @JacisApi 29 | public interface JacisModificationListener { 30 | 31 | /** 32 | * Callback method called before the prepare phase of a transaction for value modified during the transaction. 33 | * Implementations of this method may do further modifications on the passes value to commit, based on the tracked modifications. 34 | * This is done before the prepare phase of the transaction is actually started for the store. 35 | * 36 | * @param key The key of the modified object 37 | * @param oldValue The original value of the modified object when it was copied to the transactional view (read only). 38 | * @param valueToCommit The new modified value that was updated during the transaction and may be adjusted in this method (writable). 39 | * @param tx The transaction that is currently committed. 40 | */ 41 | default void onAdjustBeforePrepare(K key, V oldValue, V valueToCommit, JacisTransactionHandle tx) { 42 | // default implementation empty 43 | } 44 | 45 | /** 46 | * Callback method called during the prepare phase of a transaction for each modified value. 47 | * Note that implementing methods can throw an {@link JacisModificationVetoException} to deny the modification and rollback the transaction. 48 | * 49 | * @param key The key of the modified object 50 | * @param oldValue The original value of the modified object when it was copied to the transactional view (read only). 51 | * @param newValue The new modified value that is written back to the committed values (read only). 52 | * @param tx The transaction that is currently committed. 53 | * @throws JacisModificationVetoException to deny the modification and rollback the transaction 54 | */ 55 | default void onPrepareModification(K key, V oldValue, V newValue, JacisTransactionHandle tx) throws JacisModificationVetoException { 56 | // default implementation empty 57 | } 58 | 59 | /** 60 | * Callback method called during the commit phase of a transaction for each modified value written back 61 | * from the transactional view to the store of committed values. 62 | * Note that implementing methods should not throw an exception since the original transaction could be broken by this. 63 | * 64 | * @param key The key of the modified object 65 | * @param oldValue The original value of the modified object when it was copied to the transactional view (read only). 66 | * @param newValue The new modified value that is written back to the committed values (read only). 67 | * @param tx The transaction that is currently committed. 68 | */ 69 | void onModification(K key, V oldValue, V newValue, JacisTransactionHandle tx); 70 | 71 | /** 72 | * @return if the implementation of the modification listener is thread safe. Default is false. Overwrite this method to declare a view to be thread safe. 73 | */ 74 | default boolean isThreadSafe() { 75 | return false; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/index/JacisNonUniqueMultiIndexTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.index; 6 | 7 | import org.jacis.container.JacisContainer; 8 | import org.jacis.integration.JacisStoreIntegrationTest; 9 | import org.jacis.store.JacisStore; 10 | import org.jacis.testhelper.JacisTestHelper; 11 | import org.jacis.testhelper.TestObject; 12 | import org.junit.Test; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.Arrays; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertSame; 20 | 21 | public class JacisNonUniqueMultiIndexTest { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(JacisStoreIntegrationTest.class); 24 | 25 | @Test 26 | public void testNonUniqueMultiIndexAccessorMethods() { 27 | JacisTestHelper testHelper = new JacisTestHelper(); 28 | JacisStore store = testHelper.createTestStoreWithCloning(); 29 | JacisNonUniqueMultiIndex index = store.createNonUniqueMultiIndex("IDX-NAME", TestObject::getAllStrValue); 30 | JacisNonUniqueMultiIndex sameIndex = store.getNonUniqueMultiIndex("IDX-NAME"); 31 | log.info("index1: {} ", index); 32 | log.info("index2: {} ", sameIndex); 33 | assertSame(index, sameIndex); 34 | } 35 | 36 | @Test(expected = IllegalStateException.class) 37 | public void testNonUniqueMultiIndexCreateIndexTwoTimes() { 38 | JacisTestHelper testHelper = new JacisTestHelper(); 39 | JacisStore store = testHelper.createTestStoreWithCloning(); 40 | store.createNonUniqueMultiIndex("IDX-NAME", TestObject::getAllStrValue); 41 | store.createNonUniqueMultiIndex("IDX-NAME", TestObject::getAllStrValue); 42 | } 43 | 44 | @Test 45 | public void testNonUniqueMultiIndexSimpleAccess() { 46 | JacisTestHelper testHelper = new JacisTestHelper(); 47 | JacisStore store = testHelper.createTestStoreWithCloning(); 48 | JacisContainer container = store.getContainer(); 49 | JacisNonUniqueMultiIndex index = store.createNonUniqueMultiIndex("IDX-NAME", TestObject::getAllStrValue); 50 | container.withLocalTx(() -> { 51 | store.update("1", new TestObject("A1a").setValue(5).setStrValue("IDX-1")); 52 | store.update("2", new TestObject("A1b").setValue(5).setStrValue("IDX-1")); 53 | store.update("3", new TestObject("A2").setValue(5).setStrValue("IDX-2")); 54 | store.update("4", new TestObject("A3").setValue(5).setStrValue("IDX-3")); 55 | }); 56 | assertEquals(2, index.getReadOnly("IDX-1").size()); 57 | assertEquals(1, index.getReadOnly("IDX-2").size()); 58 | assertEquals(1, index.getReadOnly("IDX-3").size()); 59 | assertEquals(1, index.getReadOnly("A1a").size()); 60 | assertEquals(1, index.getReadOnly("A1b").size()); 61 | assertEquals(1, index.getReadOnly("A2").size()); 62 | assertEquals(1, index.getReadOnly("A3").size()); 63 | assertEquals("A2", index.getReadOnly("IDX-2").iterator().next().getName()); 64 | assertEquals("A3", index.getReadOnly("IDX-3").iterator().next().getName()); 65 | assertEquals(3, index.multiGetReadOnly(Arrays.asList("IDX-1", "IDX-2")).size()); 66 | log.info("IDX-1: {}", index.getReadOnly("IDX-1")); 67 | log.info("IDX-2: {}", index.getReadOnly("IDX-2")); 68 | log.info("IDX-3: {}", index.getReadOnly("IDX-3")); 69 | container.withLocalTx(() -> { 70 | assertEquals(2, index.get("IDX-1").size()); 71 | assertEquals(1, index.get("IDX-2").size()); 72 | assertEquals(1, index.get("IDX-3").size()); 73 | assertEquals(1, index.get("A1a").size()); 74 | assertEquals(1, index.get("A1b").size()); 75 | assertEquals(1, index.get("A2").size()); 76 | assertEquals(1, index.get("A3").size()); 77 | assertEquals("A2", index.get("IDX-2").iterator().next().getName()); 78 | assertEquals("A3", index.get("IDX-3").iterator().next().getName()); 79 | assertEquals(3, index.multiGet(Arrays.asList("IDX-1", "IDX-2")).size()); 80 | }); 81 | } 82 | 83 | @Test 84 | public void testNonUniqueMultiIndexClearedDuringClear() { 85 | JacisTestHelper testHelper = new JacisTestHelper(); 86 | JacisStore store = testHelper.createTestStoreWithCloning(); 87 | JacisContainer container = store.getContainer(); 88 | JacisNonUniqueMultiIndex idx = store.createNonUniqueMultiIndex("IDX-NAME", TestObject::getAllStrValue); 89 | assertEquals(0, idx.getReadOnly("IDX-1").size()); 90 | container.withLocalTx(() -> store.update("1", new TestObject("A1").setValue(5).setStrValue("IDX-1"))); 91 | assertEquals(1, idx.getReadOnly("IDX-1").size()); 92 | container.withLocalTx(store::clear); 93 | assertEquals(0, idx.getReadOnly("IDX-1").size()); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/extension/persistence/microstream/MicrostreamStorage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | package org.jacis.extension.persistence.microstream; 5 | 6 | import org.eclipse.store.storage.types.StorageManager; 7 | import org.jacis.container.JacisContainer.StoreIdentifier; 8 | import org.jacis.container.JacisTransactionHandle; 9 | import org.jacis.store.JacisStore; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | /** 18 | * Representing a single microstream storage manager storing to one storage directory. 19 | * A single instance of this class can be set for multiple JACIS stores managed by the same JACIs container. 20 | * In this the store operation for all committed objects of all stores is done in an atomic call after the commit. 21 | * 22 | * @author Jan Wiemer 23 | */ 24 | public class MicrostreamStorage { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(MicrostreamStorage.class); 27 | 28 | /** The Microstream storage manager used to persist entities. */ 29 | private final StorageManager storageManager; 30 | /** The root object stored by the Microstream storage manager. */ 31 | private final MicrostreamRoot storageRoot; 32 | /** For each active transaction the set of modified (wrapper) objects to store */ 33 | private final Map> objectsToStore = new HashMap<>(); 34 | 35 | public MicrostreamStorage(StorageManager storageManager) { 36 | this.storageManager = storageManager; 37 | String storageDirectory = storageManager.configuration().fileProvider().baseDirectory().toPathString(); 38 | log.debug("{} start init (storage dir: {})", this, storageDirectory); 39 | long t0 = System.nanoTime(); 40 | if (storageManager.root() == null) { 41 | storageRoot = new MicrostreamRoot(); 42 | storageManager.setRoot(storageRoot); 43 | storageManager.storeRoot(); 44 | log.debug("{} init: created new root; {}", this, storageRoot); 45 | } else { 46 | storageRoot = (MicrostreamRoot) storageManager.root(); 47 | log.debug("{} init: found existing root; {}", this, storageRoot); 48 | } 49 | log.debug("{} init finished after {} (storage root: {})", this, stopTime(t0), storageRoot); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return getClass().getSimpleName(); 55 | } 56 | 57 | public MicrostreamStoreRoot getOrCreateStoreRoot(JacisStore store) { 58 | StoreIdentifier storeIdentifier = store.getStoreIdentifier(); 59 | MicrostreamStoreRoot storeRoot = storageRoot.getStoreRoot(store); 60 | if (storeRoot != null) { 61 | log.debug("{} ... found existing store root for {}: {}", this, storeIdentifier.toShortString(), storeRoot); 62 | } else { 63 | storeRoot = new MicrostreamStoreRoot<>(storeIdentifier); 64 | storageRoot.setStoreRoot(store, storeRoot); 65 | storageManager.storeAll(storageRoot, storageRoot.getStoreRootMap(), storeRoot); 66 | log.debug("{} ... created new store root for {}: {}", this, storeIdentifier.toShortString(), storeRoot); 67 | } 68 | return storeRoot; 69 | } 70 | 71 | public void trackObjectsToStore(JacisTransactionHandle tx, Set toStore) { 72 | Set oldToStore = objectsToStore.get(tx); 73 | if (oldToStore == null) { 74 | objectsToStore.put(tx, toStore); 75 | } else { 76 | oldToStore.addAll(toStore); 77 | } 78 | } 79 | 80 | public void afterCommit(JacisTransactionHandle tx) { 81 | Set toStore = objectsToStore.remove(tx); 82 | if (log.isTraceEnabled()) { 83 | log.trace("{} after commit: store #{} objects: {}", this, toStore == null ? 0 : toStore.size(), toStore); 84 | } 85 | long t0 = System.nanoTime(); 86 | if (toStore != null && !toStore.isEmpty()) { 87 | storageManager.storeAll(toStore); 88 | log.debug("{} after commit: storing {} objects took {} (TX: {})", this, toStore.size(), stopTime(t0), tx); 89 | } 90 | } 91 | 92 | public void afterRollback(JacisTransactionHandle tx) { 93 | Set toStore = objectsToStore.remove(tx); 94 | if (log.isTraceEnabled()) { 95 | log.trace("{} after rollback: skip storing #{} objects: {}", this, toStore == null ? 0 : toStore.size(), toStore); 96 | } 97 | log.debug("{} after rollback: skip storing #{} (TX: {})", this, toStore == null ? 0 : toStore.size(), tx); 98 | } 99 | 100 | String stopTime(long startTimeNs) { 101 | long nanos = System.nanoTime() - startTimeNs; 102 | double millis = ((double) nanos) / (1000 * 1000); 103 | return String.format("%.2f ms", millis); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/JacisTransactionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer; 9 | import org.jacis.container.JacisTransactionHandle; 10 | 11 | /** 12 | * Listener that is informed on the lifecycle events of a transaction. 13 | *

14 | * Listeners implementing this interface can be registered at a JACIS container 15 | * by passing it to the method {@link JacisContainer#registerTransactionListener(JacisTransactionListener)}. 16 | * Once registered on each lifecycle event of any transaction the container has joined causes the 17 | * corresponding lifecycle callback method to be invoked. 18 | * 19 | * @author Jan Wiemer 20 | */ 21 | @JacisApi 22 | public interface JacisTransactionListener { 23 | 24 | /** 25 | * @return if this transaction listener has to be executed synchronized together with the prepare / commit / rollback (for callbacks on prepare / commit). 26 | */ 27 | default boolean isSynchronizedExcecutionRequired() { 28 | return true; 29 | } 30 | 31 | /** 32 | * @return if this transaction listener has to be executed synchronized together with the prepare / commit / rollback (for callbacks on prepare). 33 | */ 34 | default boolean isSynchronizedExcecutionRequiredForPrepare() { 35 | return isSynchronizedExcecutionRequired(); 36 | } 37 | 38 | /** 39 | * @return if this transaction listener has to be executed synchronized together with the prepare / commit / rollback (for callbacks on commit). 40 | */ 41 | default boolean isSynchronizedExcecutionRequiredForCommit() { 42 | return isSynchronizedExcecutionRequired(); 43 | } 44 | 45 | /** 46 | * @return if this transaction listener has to be executed synchronized together with the prepare / commit / rollback (for callbacks on rollback). 47 | * Note: Usually on rollback the callbacks do nothing (no changes) and therefore no synchronization is required. 48 | */ 49 | default boolean isSynchronizedExcecutionRequiredForRollback() { 50 | return false; 51 | } 52 | 53 | /** 54 | * Callback method called before the prepare phase of a transaction is executed for the stores of the container. 55 | * 56 | * @param container Reference to the corresponding container instance. 57 | * @param tx Handle for the transaction for which the callback method is invoked. 58 | */ 59 | default void beforePrepare(JacisContainer container, JacisTransactionHandle tx) { 60 | // default: do nothing 61 | } 62 | 63 | /** 64 | * Callback method called after the prepare phase of a transaction is executed for the stores of the container. 65 | * 66 | * @param container Reference to the corresponding container instance. 67 | * @param tx Handle for the transaction for which the callback method is invoked. 68 | */ 69 | default void afterPrepare(JacisContainer container, JacisTransactionHandle tx) { 70 | // default: do nothing 71 | } 72 | 73 | /** 74 | * Callback method called before the commit phase of a transaction is executed for the stores of the container. 75 | * 76 | * @param container Reference to the corresponding container instance. 77 | * @param tx Handle for the transaction for which the callback method is invoked. 78 | */ 79 | default void beforeCommit(JacisContainer container, JacisTransactionHandle tx) { 80 | // default: do nothing 81 | } 82 | 83 | /** 84 | * Callback method called after the commit phase of a transaction is executed for the stores of the container. 85 | * 86 | * @param container Reference to the corresponding container instance. 87 | * @param tx Handle for the transaction for which the callback method is invoked. 88 | */ 89 | default void afterCommit(JacisContainer container, JacisTransactionHandle tx) { 90 | // default: do nothing 91 | } 92 | 93 | /** 94 | * Callback method called before a rollback for a transaction is executed for the stores of the container. 95 | * 96 | * @param container Reference to the corresponding container instance. 97 | * @param tx Handle for the transaction for which the callback method is invoked. 98 | */ 99 | default void beforeRollback(JacisContainer container, JacisTransactionHandle tx) { 100 | // default: do nothing 101 | } 102 | 103 | /** 104 | * Callback method called after a rollback for a transaction is executed for the stores of the container. 105 | * 106 | * @param container Reference to the corresponding container instance. 107 | * @param tx Handle for the transaction for which the callback method is invoked. 108 | */ 109 | default void afterRollback(JacisContainer container, JacisTransactionHandle tx) { 110 | // default: do nothing 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/txadapter/local/JacisLocalTransaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.txadapter.local; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.container.JacisContainer; 9 | import org.jacis.container.JacisTransactionHandle; 10 | import org.jacis.exception.JacisNoTransactionException; 11 | 12 | import java.util.Objects; 13 | 14 | /** 15 | * Representing a local transaction. 16 | * If local transactions are used (the {@link JacisTransactionAdapterLocal} is registered with the container) 17 | * a local transaction can be started at the container (see {@link JacisContainer#beginLocalTransaction(String)}). 18 | * The returned instance of this class can be used to {@link #prepare()}, {@link #commit()} or {@link #rollback()} 19 | * the transaction. 20 | * 21 | * @author Jan Wiemer 22 | */ 23 | @JacisApi 24 | public class JacisLocalTransaction { 25 | 26 | /** 27 | * Unique id for the transaction (set with the constructor) 28 | */ 29 | private final String txId; 30 | /** Reference to the container this local transaction belongs to (needed to commit / rollback a transaction) */ 31 | private JacisContainer jacisContainer; 32 | /** The transaction handle for this local transaction. Transaction handles are used inside the store to represent local or external transactions. */ 33 | private JacisTransactionHandle jacisTransactionHandle; 34 | /** A flag indicating if the transaction already has been destroyed. */ 35 | private boolean destroyed = false; 36 | 37 | JacisLocalTransaction(String txId) { 38 | this.txId = txId; 39 | } 40 | 41 | JacisLocalTransaction associateWithJacisTransaction(JacisTransactionHandle txHandle, JacisContainer container) { 42 | if (!txId.equals(txHandle.getTxId())) { 43 | throw new IllegalArgumentException("Passed txHandle " + txHandle + " does not match the id of the local transaction " + txId); 44 | } 45 | this.jacisTransactionHandle = txHandle; 46 | this.jacisContainer = container; 47 | return this; 48 | } 49 | 50 | /** @return A description of the transaction (given when creating the transaction) */ 51 | public String getTxDescription() { 52 | return jacisTransactionHandle == null ? null : jacisTransactionHandle.getTxDescription(); 53 | } 54 | 55 | /** 56 | * Prepare the local transaction. 57 | * This method is calling the {@link JacisContainer#internalPrepare(JacisTransactionHandle)} method on the container 58 | * with the transaction handle associated with this local transaction. 59 | * 60 | * @throws JacisNoTransactionException Thrown if this local transaction is no longer active. 61 | */ 62 | public void prepare() throws JacisNoTransactionException { 63 | checkActive(); 64 | jacisContainer.internalPrepare(jacisTransactionHandle); 65 | } 66 | 67 | /** 68 | * Commit the local transaction. 69 | * This method is calling the {@link JacisContainer#internalCommit(JacisTransactionHandle)} method on the container 70 | * with the transaction handle associated with this local transaction. 71 | * 72 | * @throws JacisNoTransactionException Thrown if this local transaction is no longer active. 73 | */ 74 | public void commit() throws JacisNoTransactionException { 75 | checkActive(); 76 | jacisContainer.internalCommit(jacisTransactionHandle); 77 | destroy(); 78 | } 79 | 80 | /** 81 | * Rollback the local transaction. 82 | * This method is calling the {@link JacisContainer#internalRollback(JacisTransactionHandle)} method on the container 83 | * with the transaction handle associated with this local transaction. 84 | * 85 | * @throws JacisNoTransactionException Thrown if this local transaction is no longer active. 86 | */ 87 | public void rollback() throws JacisNoTransactionException { 88 | checkActive(); 89 | jacisContainer.internalRollback(jacisTransactionHandle); 90 | destroy(); 91 | } 92 | 93 | private void checkActive() throws JacisNoTransactionException { 94 | if (destroyed) { 95 | throw new JacisNoTransactionException("Transaction already destroyed: " + this); 96 | } 97 | } 98 | 99 | private void destroy() { 100 | jacisContainer = null; 101 | jacisTransactionHandle = null; 102 | destroyed = true; 103 | } 104 | 105 | @Override 106 | public int hashCode() { 107 | return txId.hashCode(); 108 | } 109 | 110 | @Override 111 | public boolean equals(Object obj) { 112 | if (this == obj) { 113 | return true; 114 | } else if (obj == null) { 115 | return false; 116 | } else if (getClass() != obj.getClass()) { 117 | return false; 118 | } 119 | JacisLocalTransaction that = (JacisLocalTransaction) obj; 120 | return Objects.equals(txId, that.txId); 121 | } 122 | 123 | @Override 124 | public String toString() { 125 | return "TX(" + txId + ")"; 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/index/JacisIndexRegistryTxView.java: -------------------------------------------------------------------------------- 1 | package org.jacis.index; 2 | 3 | import java.util.*; 4 | import java.util.function.Function; 5 | 6 | /** 7 | * Representing the transactional view on the indexes registered at the store for one transaction. 8 | * The transactional view stores the mapping of the index keys to the primary keys of the store 9 | * modified (due to modifications on the objects) inside the corresponding transaction. 10 | * Note that only modifications notified to the store via the update method are tracked at the indices. 11 | * 12 | * @param Key type of the store entry 13 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 14 | * @author Jan Wiemer 15 | */ 16 | public class JacisIndexRegistryTxView { 17 | 18 | /** Reference to the index registry storing all indices registered for a store. */ 19 | private final JacisIndexRegistry indexRegistry; 20 | /** Map containing the primary keys added for each index key inside the transaction for each non-unique index. */ 21 | private final Map>> nonUniqueIndexAddMap = new HashMap<>(); 22 | /** Map containing the primary keys removed for each index key inside the transaction for each non-unique index. */ 23 | private final Map>> nonUniqueIndexDelMap = new HashMap<>(); 24 | /** Map containing the unique index data for each unique index. */ 25 | private final Map>> uniqueIndexDataMap = new HashMap<>(); 26 | 27 | public JacisIndexRegistryTxView(JacisIndexRegistry indexRegistry) { 28 | this.indexRegistry = indexRegistry; 29 | } 30 | 31 | public void onTxLocalUpdate(K key, TV oldValue, TV newValue) { 32 | for (JacisNonUniqueIndex idx : indexRegistry.getNonUniqueIndexDefinitions()) { 33 | Function indexKeyFunction = idx.getIndexKeyFunction(); 34 | Object oldIndexKey = oldValue == null ? null : indexKeyFunction.apply(oldValue); 35 | Object newIndexKey = newValue == null ? null : indexKeyFunction.apply(newValue); 36 | Map> addMap = nonUniqueIndexAddMap.computeIfAbsent(idx.getIndexName(), k -> new HashMap<>()); 37 | Map> delMap = nonUniqueIndexDelMap.computeIfAbsent(idx.getIndexName(), k -> new HashMap<>()); 38 | if (oldIndexKey == null && newIndexKey == null) { 39 | continue; 40 | } else if (oldIndexKey != null && oldIndexKey.equals(newIndexKey)) { 41 | continue; 42 | } 43 | if (oldIndexKey != null) { 44 | Set oldPrimaryKeyAddSet = addMap.get(oldIndexKey); 45 | Set oldPrimaryKeyDelSet = delMap.computeIfAbsent(oldIndexKey, k -> new HashSet<>()); 46 | if (oldPrimaryKeyAddSet != null) { 47 | oldPrimaryKeyAddSet.remove(key); 48 | } 49 | oldPrimaryKeyDelSet.add(key); 50 | } 51 | if (newIndexKey != null) { 52 | Set oldPrimaryKeyAddSet = addMap.computeIfAbsent(newIndexKey, k -> new HashSet<>()); 53 | Set oldPrimaryKeyDelSet = delMap.get(newIndexKey); 54 | oldPrimaryKeyAddSet.add(key); 55 | if (oldPrimaryKeyDelSet != null) { 56 | oldPrimaryKeyDelSet.remove(key); 57 | } 58 | } 59 | } 60 | for (JacisUniqueIndex idx : indexRegistry.getUniqueIndexDefinitions()) { 61 | Function indexKeyFunction = idx.getIndexKeyFunction(); 62 | Object oldIndexKey = oldValue == null ? null : indexKeyFunction.apply(oldValue); 63 | Object newIndexKey = newValue == null ? null : indexKeyFunction.apply(newValue); 64 | if (oldIndexKey == null && newIndexKey == null) { 65 | continue; 66 | } else if (oldIndexKey != null && oldIndexKey.equals(newIndexKey)) { 67 | continue; 68 | } 69 | Map> indexMap = uniqueIndexDataMap.computeIfAbsent(idx.getIndexName(), k -> new HashMap<>()); 70 | indexRegistry.checkUniqueIndexProperty(idx, newIndexKey, key, true); 71 | if (oldIndexKey != null) { 72 | indexMap.put(oldIndexKey, Optional.empty()); 73 | } 74 | if (newIndexKey != null) { 75 | indexMap.put(newIndexKey, Optional.of(key)); 76 | } 77 | } 78 | } 79 | 80 | @java.lang.SuppressWarnings("java:S2789") 81 | public Optional getPrimaryKeyFromUniqueIndex(String indexName, Object indexKey) { 82 | Map> indexMap = uniqueIndexDataMap.get(indexName); 83 | return indexMap == null ? null : indexMap.get(indexKey); 84 | } 85 | 86 | public Set getPrimaryKeysAddedForNonUniqueIndex(String indexName, Object indexKey) { 87 | Map> addMap = nonUniqueIndexAddMap.get(indexName); 88 | return addMap == null ? Collections.emptySet() : addMap.getOrDefault(indexKey, Collections.emptySet()); 89 | } 90 | 91 | public Set getPrimaryKeysDeletedForNonUniqueIndex(String indexName, Object indexKey) { 92 | Map> delMap = nonUniqueIndexDelMap.get(indexName); 93 | return delMap == null ? Collections.emptySet() : delMap.getOrDefault(indexKey, Collections.emptySet()); 94 | } 95 | 96 | public boolean isTrackingRequired() { 97 | return indexRegistry.hasAnyRegisteredIndex(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jan.wiemer@online.de. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/store/StoreEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.store; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * Representing a committed version of an entry in the store. 11 | * 12 | * @param Key type of the store entry 13 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 14 | * @param Type of the objects as they are stored in the internal map of committed values. This type is not visible from the outside. 15 | * @author Jan Wiemer 16 | */ 17 | class StoreEntry { 18 | 19 | /** reference to the main store */ 20 | private final JacisStoreAdminInterface store; 21 | /** the key of this entry */ 22 | private final K key; 23 | /** the current committed valued of this entry (visible to all transactions) (null if not existing / deleted) */ 24 | private CV value = null; 25 | /** version counter will be increased when an updated view of the entry is committed (used for optimistic locking) */ 26 | private long version = 0; 27 | /** id of the transaction that has committed the current version (for logging / debugging only) */ 28 | private String updatedBy = null; 29 | /** name of the thread that has committed the current version (for logging / debugging only) */ 30 | private String updatedByThread = null; 31 | /** transaction this object is locked for (in the time between prepare and internalCommit) */ 32 | private JacisStoreTxView lockedFor = null; 33 | /** name of the thread that this object is locked for (in the time between prepare and internalCommit) */ 34 | private String lockedForThread = null; 35 | /** The number of TX views having a transaction local view for this committed object */ 36 | private volatile int txViewReferenceCount = 0; 37 | 38 | StoreEntry(JacisStoreAdminInterface store, K key) { 39 | this.store = store; 40 | this.key = key; 41 | } 42 | 43 | StoreEntry(JacisStoreAdminInterface store, K key, TV value) { // only for the initial value 44 | this.store = store; 45 | this.key = key; 46 | this.value = store.getObjectAdapter().cloneTxView2Committed(value); 47 | } 48 | 49 | void referencedByTxView() { // anyway synchronized on ConcurrentHashMap entry in JacisStoreImpl.updateCommittedEntry 50 | txViewReferenceCount++; 51 | } 52 | 53 | boolean unreferencedByTxViewAnCheckIfGarbage() { // anyway synchronized on ConcurrentHashMap entry in JacisStoreImpl.updateCommittedEntry 54 | txViewReferenceCount--; 55 | return txViewReferenceCount <= 0 && value == null && lockedFor == null; // garbage 56 | } 57 | 58 | public int getTxViewReferenceCount() { 59 | return txViewReferenceCount; 60 | } 61 | 62 | @SuppressWarnings("ObjectEquality") 63 | synchronized public void update(StoreEntryTxView entryTxView, JacisStoreTxView byTx) { 64 | TV txVal = entryTxView.getValue(); 65 | if (txVal == null) { // deleted 66 | value = null; 67 | } else if (txVal != value) { // intentionally checked if both instances are different (and not used equals!) 68 | value = store.getObjectAdapter().cloneTxView2Committed(txVal); 69 | } 70 | version++; 71 | updatedBy = byTx.getTxId(); 72 | updatedByThread = Thread.currentThread().getName(); 73 | } 74 | 75 | synchronized void lockedFor(JacisStoreTxView lockingTx) { 76 | lockedFor = lockingTx; 77 | lockedForThread = Thread.currentThread().getName(); 78 | } 79 | 80 | synchronized void releaseLockedFor(JacisStoreTxView releasingTx) { 81 | if (releasingTx.equals(getLockedFor())) { 82 | lockedFor = null; 83 | lockedForThread = null; 84 | } 85 | } 86 | 87 | synchronized boolean isLocked() { 88 | return lockedFor != null; 89 | } 90 | 91 | synchronized boolean isLockedForOtherThan(JacisStoreTxView txView) { 92 | JacisStoreTxView lf = lockedFor; 93 | return lf != null && !lf.equals(txView); 94 | } 95 | 96 | JacisStoreAdminInterface getStore() { 97 | return store; 98 | } 99 | 100 | K getKey() { 101 | return key; 102 | } 103 | 104 | synchronized CV getValue() { 105 | return value; 106 | } 107 | 108 | synchronized boolean isNull() { 109 | return value == null; 110 | } 111 | 112 | synchronized boolean isNotNull() { 113 | return value != null; 114 | } 115 | 116 | synchronized long getVersion() { 117 | return version; 118 | } 119 | 120 | synchronized String getUpdatedByTxId() { 121 | return updatedBy; 122 | } 123 | 124 | synchronized String getUpdatedByThread() { 125 | return updatedByThread; 126 | } 127 | 128 | synchronized JacisStoreTxView getLockedFor() { 129 | return lockedFor; 130 | } 131 | 132 | synchronized String getLockedForThread() { 133 | return lockedForThread; 134 | } 135 | 136 | @Override 137 | public int hashCode() { 138 | return key.hashCode(); 139 | } 140 | 141 | @Override 142 | public boolean equals(Object obj) { 143 | if (this == obj) { 144 | return true; 145 | } else if (obj == null) { 146 | return false; 147 | } else if (getClass() != obj.getClass()) { 148 | return false; 149 | } 150 | StoreEntry that = (StoreEntry) obj; 151 | return Objects.equals(key, that.key); 152 | } 153 | 154 | @Override 155 | synchronized public String toString() { 156 | StringBuilder b = new StringBuilder(); 157 | b.append(key).append("->").append(value).append(" (v.").append(version).append(")"); 158 | JacisStoreTxView lf = lockedFor; 159 | if (lf != null) { 160 | b.append("lockedFor:").append(lf); 161 | } 162 | return b.toString(); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/index/JacisUniqueIndex.java: -------------------------------------------------------------------------------- 1 | package org.jacis.index; 2 | 3 | import org.jacis.JacisApi; 4 | import org.jacis.exception.JacisUniqueIndexViolationException; 5 | import org.jacis.store.JacisStore; 6 | 7 | import java.util.Collection; 8 | import java.util.function.Function; 9 | import java.util.function.Predicate; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * Represents an index providing access to the values stored in the JACIS store by an index key. 14 | * The class represents a unique index, therefore for each index key only one value of the original store is returned. 15 | * Note that if a unique index is registered at a store a commit changing a value in a way that the unique index is violated 16 | * an {{@link JacisUniqueIndexViolationException}} is thrown. 17 | *

18 | * Note that modifications inside the active transactions will be reflected by the index 19 | * if (and only if) the modification is notified to the store by the update method. 20 | * 21 | * @param Index key type for this unique index 22 | * @param Key type of the store entry 23 | * @param Type of the objects in the transaction view. This is the type visible from the outside. 24 | * @author Jan Wiemer 25 | */ 26 | @JacisApi 27 | public class JacisUniqueIndex extends AbstractJacisIndex { 28 | 29 | JacisUniqueIndex(String indexName, Function indexKeyFunction, JacisIndexRegistry indexRegistry) { 30 | super(indexName, indexKeyFunction, indexRegistry); 31 | } 32 | 33 | /** 34 | * Returns the unique primary key for the passed index key. 35 | * The primary key is the key used to store the object in the store. 36 | * 37 | * @param indexKey The index key of the desired entry. 38 | * @return the unique primary key for the passed index key. 39 | */ 40 | public K getPrimaryKey(IK indexKey) { 41 | return indexRegistry.getFromUniqueIndexPrimaryKey(this, indexKey); 42 | } 43 | 44 | /** 45 | * Returns the unique (writable) value for the passed index key. 46 | * For details see the {@link JacisStore#get(Object)} method. 47 | * 48 | * @param indexKey The index key of the desired entry. 49 | * @return the unique value for the passed index key. 50 | */ 51 | public TV get(IK indexKey) { 52 | return indexRegistry.getFromUniqueIndex(this, indexKey); 53 | } 54 | 55 | /** 56 | * Returns the unique read only value for the passed index key. 57 | * For details see the {@link JacisStore#getReadOnly(Object)} method. 58 | * 59 | * @param indexKey The index key of the desired entry. 60 | * @return the unique read only value for the passed key. 61 | */ 62 | public TV getReadOnly(IK indexKey) { 63 | return indexRegistry.getFromUniqueIndexReadOnly(this, indexKey); 64 | } 65 | 66 | /** 67 | * Returns a stream of the unique (writable) values for the passed index keys. 68 | * For details see the {@link JacisStore#get(Object)} method. 69 | * 70 | * @param indexKeys The index keys of the desired entrys. 71 | * @return a stream of the unique values for the passed index keys. 72 | */ 73 | public Stream stream(Collection indexKeys) { 74 | return indexRegistry.streamFromUniqueIndex(this, indexKeys); 75 | } 76 | 77 | /** 78 | * Returns a stream of the unique (writable) values for the passed index keys. 79 | * For details see the {@link JacisStore#get(Object)} method. 80 | * Before cloning the stored read-only instances into the transaction view 81 | * the passed filter is applied on the read-only instances. 82 | * 83 | * @param indexKeys The index keys of the desired entrys. 84 | * @param filter A filter applied to the read-only instances before cloning and returning them. 85 | * @return a stream of the unique values for the passed index keys. 86 | */ 87 | public Stream stream(Collection indexKeys, Predicate filter) { 88 | return indexRegistry.streamFromUniqueIndex(this, indexKeys, filter); 89 | } 90 | 91 | /** 92 | * Returns the unique (writable) values for the passed index keys. 93 | * For details see the {@link JacisStore#get(Object)} method. 94 | * 95 | * @param indexKeys The index keys of the desired entrys. 96 | * @return the collection of unique values for the passed index keys. 97 | */ 98 | public Collection multiGet(Collection indexKeys) { 99 | return indexRegistry.multiGetFromUniqueIndex(this, indexKeys); 100 | } 101 | 102 | /** 103 | * Returns the unique (writable) values for the passed index keys. 104 | * For details see the {@link JacisStore#get(Object)} method. 105 | * Before cloning the stored read-only instances into the transaction view 106 | * the passed filter is applied on the read-only instances. 107 | * 108 | * @param indexKeys The index keys of the desired entries. 109 | * @param filter A filter applied to the read-only instances before cloning and returning them. 110 | * @return the collection of unique values for the passed index keys. 111 | */ 112 | public Collection multiGet(Collection indexKeys, Predicate filter) { 113 | return indexRegistry.multiGetFromUniqueIndex(this, indexKeys, filter); 114 | } 115 | 116 | /** 117 | * Returns a stream of the unique read only values for the passed index keys. 118 | * For details see the {@link JacisStore#getReadOnly(Object)} method. 119 | * 120 | * @param indexKeys The index keys of the desired entries. 121 | * @return a stream of the unique read only values for the passed keys. 122 | */ 123 | public Stream streamReadOnly(Collection indexKeys) { 124 | return indexRegistry.streamFromUniqueIndexReadOnly(this, indexKeys); 125 | } 126 | 127 | /** 128 | * Returns the unique read only values for the passed index keys. 129 | * For details see the {@link JacisStore#getReadOnly(Object)} method. 130 | * 131 | * @param indexKeys The index keys of the desired entries. 132 | * @return the collection of unique read only values for the passed keys. 133 | */ 134 | public Collection multiGetReadOnly(Collection indexKeys) { 135 | return indexRegistry.multiGetFromUniqueIndexReadOnly(this, indexKeys); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/jacis/plugin/objectadapter/cloning/AbstractJacisCloningObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis.plugin.objectadapter.cloning; 6 | 7 | import org.jacis.JacisApi; 8 | import org.jacis.exception.ReadOnlyModeNotSupportedException; 9 | import org.jacis.plugin.objectadapter.JacisObjectAdapter; 10 | import org.jacis.plugin.readonly.DefaultJacisStoreEntryReadOnlyModeAdapter; 11 | import org.jacis.plugin.readonly.JacisStoreEntryReadOnlyModeAdapter; 12 | 13 | /** 14 | * Abstract generic implementation of the {@link org.jacis.plugin.objectadapter.JacisObjectAdapter} 15 | * copying the objects to and from the transactional view by cloning the object. 16 | * The method used to clone the object is left to the derived class implementing the {@link #cloneValue(Object)} method. 17 | * 18 | *

19 | * If a read only mode adapter is set (see {@link #readOnlyModeAdapter}) and this read only mode adapter is applicable 20 | * for the objects in the store this adapter is used to switch the objects between the read-only and read-write mode. 21 | * In this case the committed values are always stored in read-only mode. When cloned to a transactional view 22 | * the cloned values are switched to read-write mode. When the objects are cloned back to the committed view 23 | * they are switched back to the read only mode. With this feature it is possible to access a read only view 24 | * of an object from the outside. This read only view is *not* cloned from the committed value. 25 | * Instead, the committed value itself is returned to the caller. 26 | * This is possible since the object is in read only mode. 27 | *

28 | * If the read only mode is not supported accessing a read only value returns an ordinary cloned object. 29 | * 30 | * @param The object type (note that in this case the committed values and the values in the transactional view have the same type) 31 | * @author Jan Wiemer 32 | */ 33 | @JacisApi 34 | public abstract class AbstractJacisCloningObjectAdapter implements JacisObjectAdapter { 35 | 36 | /** Read only mode adapter used to switch objects from writable to read only mode if required and supported */ 37 | private final JacisStoreEntryReadOnlyModeAdapter readOnlyModeAdapter; 38 | /** Flag indicating if the object adapter should throw an exception if a read only mode is required, but not supported. */ 39 | private boolean throwIfMissingReadOnlyModeDetected = false; 40 | 41 | /** 42 | * Create a cloning object adapter with the passed read only mode adapter. 43 | * 44 | * @param readOnlyModeAdapters Adapter to switch an object between read-only and read-write mode (if supported). 45 | */ 46 | @SuppressWarnings("WeakerAccess") 47 | public AbstractJacisCloningObjectAdapter(JacisStoreEntryReadOnlyModeAdapter readOnlyModeAdapters) { 48 | this.readOnlyModeAdapter = readOnlyModeAdapters; 49 | } 50 | 51 | /** 52 | * Create a cloning object adapter with a default read only mode adapter (see {@link DefaultJacisStoreEntryReadOnlyModeAdapter}). 53 | */ 54 | public AbstractJacisCloningObjectAdapter() { 55 | this(new DefaultJacisStoreEntryReadOnlyModeAdapter<>()); 56 | } 57 | 58 | /** 59 | * Set the flag indicating if the object adapter should throw an exception if a read only mode is required, but not supported. 60 | * 61 | * @param throwIfMissingReadOnlyModeDetected flag indicating if the object adapter should throw an exception if a read only mode is required, but not supported. 62 | * @return The current instance for method chaining 63 | */ 64 | public AbstractJacisCloningObjectAdapter setThrowIfMissingReadOnlyModeDetected(boolean throwIfMissingReadOnlyModeDetected) { 65 | this.throwIfMissingReadOnlyModeDetected = throwIfMissingReadOnlyModeDetected; 66 | return this; 67 | } 68 | 69 | @Override 70 | public V cloneCommitted2WritableTxView(V value) { 71 | if (value == null) { 72 | return null; 73 | } 74 | V clone = cloneValue(value); 75 | if (readOnlyModeAdapter != null && readOnlyModeAdapter.isApplicableTo(clone)) { 76 | clone = readOnlyModeAdapter.switchToReadWriteMode(clone); 77 | } 78 | return clone; 79 | } 80 | 81 | @Override 82 | public V cloneTxView2Committed(V value) { 83 | if (value == null) { 84 | return null; 85 | } 86 | V clone = cloneValue(value); 87 | if (readOnlyModeAdapter != null && readOnlyModeAdapter.isApplicableTo(clone)) { 88 | clone = readOnlyModeAdapter.switchToReadOnlyMode(clone); 89 | } 90 | return clone; 91 | } 92 | 93 | @Override 94 | public V cloneCommitted2ReadOnlyTxView(V value) { 95 | if (value == null) { 96 | return null; 97 | } 98 | checkReadOnlyModeSupported(value); 99 | V clone; 100 | if (readOnlyModeAdapter == null || !readOnlyModeAdapter.isApplicableTo(value)) { 101 | clone = cloneValue(value); 102 | } else { 103 | clone = value; // for read only objects we do not clone the returned object. -> skip call of: cloneValue(value); 104 | } 105 | return clone; 106 | } 107 | 108 | @Override 109 | public V cloneTxView2ReadOnlyTxView(V value) { 110 | if (value == null) { 111 | return null; 112 | } 113 | if (readOnlyModeAdapter != null && readOnlyModeAdapter.isApplicableTo(value)) { 114 | if (readOnlyModeAdapter.isReadOnly(value)) { 115 | return value; 116 | } else { 117 | return readOnlyModeAdapter.switchToReadOnlyMode(cloneValue(value)); 118 | } 119 | } else { 120 | return cloneValue(value); 121 | } 122 | } 123 | 124 | private void checkReadOnlyModeSupported(V value) throws ReadOnlyModeNotSupportedException { 125 | if (throwIfMissingReadOnlyModeDetected && value != null && (readOnlyModeAdapter == null || !readOnlyModeAdapter.isApplicableTo(value))) { 126 | throw new ReadOnlyModeNotSupportedException("Object of class " + value.getClass().getName() + " not supporting read only mode! Object: " + value); 127 | } 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return getClass().getSimpleName() + "(readOnlyModeAdapter=" + readOnlyModeAdapter + ", throwIfMissingReadOnlyModeDetected=" + throwIfMissingReadOnlyModeDetected + ")"; 133 | } 134 | 135 | protected abstract V cloneValue(V value); 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/org/jacis/readonlytxview/ReadOnlyTxViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. Jan Wiemer 3 | */ 4 | package org.jacis.readonlytxview; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | import java.util.concurrent.atomic.AtomicReference; 9 | 10 | import org.jacis.container.JacisContainer; 11 | import org.jacis.container.JacisContainerReadOnlyTransactionContext; 12 | import org.jacis.plugin.txadapter.local.JacisLocalTransaction; 13 | import org.jacis.store.JacisStore; 14 | import org.jacis.testhelper.JacisTestHelper; 15 | import org.jacis.testhelper.TestObject; 16 | import org.junit.Test; 17 | 18 | public class ReadOnlyTxViewTest { 19 | 20 | @Test 21 | public void testSimpleUsageOfReadOnlyTxContext() { 22 | JacisTestHelper testHelper = new JacisTestHelper(); 23 | JacisStore store = testHelper.createTestStoreWithCloning(); 24 | JacisContainer container = store.getContainer(); 25 | container.withLocalTx(() -> { 26 | store.update("1", new TestObject("A1").setValue(10)); 27 | store.update("2", new TestObject("A2").setValue(20)); 28 | store.update("3", new TestObject("A3").setValue(30)); 29 | store.update("4", new TestObject("A4").setValue(40)); 30 | store.update("5", new TestObject("A5").setValue(50)); 31 | }); 32 | AtomicReference roTxView = new AtomicReference<>(); 33 | container.withLocalTx(() -> { 34 | roTxView.set(container.createReadOnlyTransactionView("TestTx2")); 35 | }); 36 | JacisLocalTransaction roTx = container.beginLocalTransaction("roTx"); 37 | container.startReadOnlyTransactionWithContext(roTxView.get()); 38 | assertEquals(10, store.get("1").getValue()); 39 | assertEquals(20, store.get("2").getValue()); 40 | assertEquals(30, store.get("3").getValue()); 41 | assertEquals(40, store.get("4").getValue()); 42 | assertEquals(50, store.get("5").getValue()); 43 | roTx.rollback(); 44 | } 45 | 46 | @Test 47 | public void testUsageOfReadOnlyTxContextWithLocalModification() { 48 | JacisTestHelper testHelper = new JacisTestHelper(); 49 | JacisStore store = testHelper.createTestStoreWithCloning(); 50 | JacisContainer container = store.getContainer(); 51 | container.withLocalTx(() -> { 52 | store.update("1", new TestObject("A1").setValue(10)); 53 | store.update("2", new TestObject("A2").setValue(20)); 54 | store.update("3", new TestObject("A3").setValue(30)); 55 | store.update("4", new TestObject("A4").setValue(40)); 56 | store.update("5", new TestObject("A5").setValue(50)); 57 | }); 58 | AtomicReference roTxView = new AtomicReference<>(); 59 | container.withLocalTx(() -> { 60 | store.update("5", store.get("5").setValue(51)); 61 | roTxView.set(container.createReadOnlyTransactionView("TestTx2")); 62 | }); 63 | JacisLocalTransaction roTx = container.beginLocalTransaction("roTx"); 64 | container.startReadOnlyTransactionWithContext(roTxView.get()); 65 | assertEquals(10, store.get("1").getValue()); 66 | assertEquals(20, store.get("2").getValue()); 67 | assertEquals(30, store.get("3").getValue()); 68 | assertEquals(40, store.get("4").getValue()); 69 | assertEquals(51, store.get("5").getValue()); 70 | roTx.rollback(); 71 | } 72 | 73 | @Test 74 | public void testUsageOfReadOnlyTxContextWithLocalModificationWithoutUpdate() { 75 | JacisTestHelper testHelper = new JacisTestHelper(); 76 | JacisStore store = testHelper.createTestStoreWithCloning(); 77 | JacisContainer container = store.getContainer(); 78 | container.withLocalTx(() -> { 79 | store.update("1", new TestObject("A1").setValue(10)); 80 | store.update("2", new TestObject("A2").setValue(20)); 81 | store.update("3", new TestObject("A3").setValue(30)); 82 | store.update("4", new TestObject("A4").setValue(40)); 83 | store.update("5", new TestObject("A5").setValue(50)); 84 | }); 85 | AtomicReference roTxView = new AtomicReference<>(); 86 | container.withLocalTx(() -> { 87 | store.get("5").setValue(51); 88 | roTxView.set(container.createReadOnlyTransactionView("TestTx2")); 89 | }); 90 | JacisLocalTransaction roTx = container.beginLocalTransaction("roTx"); 91 | container.startReadOnlyTransactionWithContext(roTxView.get()); 92 | assertEquals(10, store.get("1").getValue()); 93 | assertEquals(20, store.get("2").getValue()); 94 | assertEquals(30, store.get("3").getValue()); 95 | assertEquals(40, store.get("4").getValue()); 96 | assertEquals(51, store.get("5").getValue()); 97 | roTx.rollback(); 98 | } 99 | 100 | @Test 101 | public void testUsageOfReadOnlyTxContextInNestedTransaction() { 102 | JacisTestHelper testHelper = new JacisTestHelper(); 103 | JacisStore store = testHelper.createTestStoreWithCloning(); 104 | JacisContainer container = store.getContainer(); 105 | container.withLocalTx(() -> { 106 | store.update("1", new TestObject("A1").setValue(10)); 107 | store.update("2", new TestObject("A2").setValue(20)); 108 | store.update("3", new TestObject("A3").setValue(30)); 109 | store.update("4", new TestObject("A4").setValue(40)); 110 | store.update("5", new TestObject("A5").setValue(50)); 111 | }); 112 | AtomicReference roTxView = new AtomicReference<>(); 113 | container.withLocalTx(() -> { 114 | store.get("5").setValue(51); 115 | roTxView.set(container.createReadOnlyTransactionView("TestTx2")); 116 | Thread thread = new Thread(() -> { 117 | JacisLocalTransaction roTx = container.beginLocalTransaction("roTx"); 118 | container.startReadOnlyTransactionWithContext(roTxView.get()); 119 | assertEquals(10, store.get("1").getValue()); 120 | assertEquals(20, store.get("2").getValue()); 121 | assertEquals(30, store.get("3").getValue()); 122 | assertEquals(40, store.get("4").getValue()); 123 | assertEquals(51, store.get("5").getValue()); 124 | roTx.rollback(); 125 | }); 126 | thread.start(); 127 | try { 128 | thread.join(); 129 | } catch (InterruptedException e) { 130 | Thread.currentThread().interrupt(); 131 | } 132 | }); 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/test/java/org/jacis/JacisContainerLocalTxTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Jan Wiemer 3 | */ 4 | 5 | package org.jacis; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.assertTrue; 10 | import static org.junit.Assert.fail; 11 | 12 | import org.jacis.container.JacisContainer; 13 | import org.jacis.container.JacisTransactionHandle; 14 | import org.jacis.exception.JacisNoTransactionException; 15 | import org.jacis.exception.JacisTransactionAlreadyStartedException; 16 | import org.jacis.plugin.txadapter.local.JacisLocalTransaction; 17 | import org.jacis.store.JacisStore; 18 | import org.jacis.testhelper.JacisTestHelper; 19 | import org.jacis.testhelper.TestObject; 20 | import org.junit.Test; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | public class JacisContainerLocalTxTest { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(JacisContainerLocalTxTest.class); 27 | 28 | private void assertNoTransaction(JacisContainer container) { 29 | try { 30 | JacisTransactionHandle tx = container.getCurrentTransaction(true); 31 | fail("No transaction expected but found: " + tx); 32 | } catch (JacisNoTransactionException e) { 33 | // expected 34 | } 35 | } 36 | 37 | @Test 38 | public void testInitiallyNoTransaction() { 39 | JacisContainer container = new JacisContainer(); 40 | assertNoTransaction(container); 41 | } 42 | 43 | @Test 44 | public void testBeginCommit() { 45 | JacisContainer container = new JacisContainer(); 46 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 47 | assertTrue(container.isInTransaction()); 48 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 49 | assertTrue(container.isInTransaction()); 50 | tx.commit(); 51 | assertFalse(container.isInTransaction()); 52 | assertNoTransaction(container); 53 | } 54 | 55 | @Test 56 | public void testBeginRollback() { 57 | JacisContainer container = new JacisContainer(); 58 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 59 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 60 | tx.rollback(); 61 | assertNoTransaction(container); 62 | } 63 | 64 | @Test 65 | public void testBeginPrepareCommit() { 66 | JacisContainer container = new JacisContainer(); 67 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 68 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 69 | tx.prepare(); 70 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 71 | tx.commit(); 72 | assertNoTransaction(container); 73 | } 74 | 75 | @Test 76 | public void testBeginPrepareRollback() { 77 | JacisContainer container = new JacisContainer(); 78 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 79 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 80 | tx.prepare(); 81 | assertEquals(tx, container.getCurrentTransaction(true).getExternalTransaction()); 82 | tx.rollback(); 83 | assertNoTransaction(container); 84 | } 85 | 86 | @Test(expected = JacisTransactionAlreadyStartedException.class) 87 | public void testBeginBegin() { 88 | JacisContainer container = new JacisContainer(); 89 | container.beginLocalTransaction("test"); 90 | container.beginLocalTransaction("test"); 91 | } 92 | 93 | @Test(expected = JacisTransactionAlreadyStartedException.class) 94 | public void testBeginPrepareBegin() { 95 | JacisContainer container = new JacisContainer(); 96 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 97 | tx.prepare(); 98 | container.beginLocalTransaction("test"); 99 | } 100 | 101 | @Test(expected = JacisNoTransactionException.class) 102 | public void testBeginCommitCommit() { 103 | JacisContainer container = new JacisContainer(); 104 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 105 | tx.commit(); 106 | tx.commit(); 107 | } 108 | 109 | @Test(expected = JacisNoTransactionException.class) 110 | public void testBeginCommitRollback() { 111 | JacisContainer container = new JacisContainer(); 112 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 113 | tx.commit(); 114 | tx.rollback(); 115 | } 116 | 117 | @Test(expected = JacisNoTransactionException.class) 118 | public void testBeginRollbackCommit() { 119 | JacisContainer container = new JacisContainer(); 120 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 121 | tx.rollback(); 122 | tx.commit(); 123 | } 124 | 125 | @Test(expected = JacisNoTransactionException.class) 126 | public void testBeginRollbackRollback() { 127 | JacisContainer container = new JacisContainer(); 128 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 129 | tx.rollback(); 130 | tx.rollback(); 131 | } 132 | 133 | @Test(expected = JacisNoTransactionException.class) 134 | public void testInsertWithoutTransaction() { 135 | JacisContainer container = new JacisContainer(); 136 | JacisStore store = new JacisTestHelper().createTestStoreWithCloning(container); 137 | store.update("obj-1", new TestObject("obj-1", 1)); 138 | } 139 | 140 | @Test() 141 | public void testInsertWithinTransaction() { 142 | JacisContainer container = new JacisContainer(); 143 | JacisStore store = new JacisTestHelper().createTestStoreWithCloning(container); 144 | JacisLocalTransaction tx = container.beginLocalTransaction("test"); 145 | store.update("obj-1", new TestObject("obj-1", 1)); 146 | tx.commit(); 147 | } 148 | 149 | @Test() 150 | public void testTransactionDescription() { 151 | JacisContainer container = new JacisContainer(); 152 | JacisLocalTransaction tx = container.beginLocalTransaction(); 153 | log.info("Transaction: {}", tx); 154 | assertTrue(tx.getTxDescription().contains(JacisContainerLocalTxTest.class.getName())); 155 | tx.commit(); 156 | container.withLocalTxAndRetry(5, () -> { 157 | JacisTransactionHandle tx2 = container.getCurrentTransaction(false); 158 | log.info("Transaction2: {}", tx2); 159 | assertTrue(tx2.getTxDescription().contains(JacisContainerLocalTxTest.class.getName())); 160 | }); 161 | } 162 | 163 | } 164 | --------------------------------------------------------------------------------