From 9dc09515e95a944f8361c4e0f023df85c15840d5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 5 Aug 2016 20:24:23 +0200 Subject: [PATCH] [Major] Implemented opt-in versioning Now all root elements have a version, and if the realm has versioning enabled, then actions through the ElementMap lead to new versions being created. There are also methods to revert/undo changes to an object. Some tests are still failing, this will be fixed later --- .../java/li/strolch/agent/api/ElementMap.java | 379 +++++++++++++++++- .../li/strolch/agent/api/StrolchRealm.java | 2 + .../agent/impl/AuditingElementMapFacade.java | 90 ++++- .../strolch/agent/impl/CachedActivityMap.java | 8 +- .../strolch/agent/impl/CachedElementMap.java | 290 +++++++++++--- .../li/strolch/agent/impl/CachedOrderMap.java | 8 +- .../li/strolch/agent/impl/CachedRealm.java | 13 +- .../strolch/agent/impl/CachedResourceMap.java | 8 +- .../agent/impl/DefaultRealmHandler.java | 3 + .../li/strolch/agent/impl/EmptyRealm.java | 16 +- .../agent/impl/InternalStrolchRealm.java | 58 ++- .../agent/impl/TransactionalActivityMap.java | 5 + .../agent/impl/TransactionalAuditTrail.java | 2 +- .../agent/impl/TransactionalElementMap.java | 271 +++++++++++-- .../agent/impl/TransactionalOrderMap.java | 5 + .../agent/impl/TransactionalRealm.java | 12 +- .../agent/impl/TransactionalResourceMap.java | 5 + .../li/strolch/agent/impl/TransientRealm.java | 15 +- .../persistence/api/AbstractTransaction.java | 62 ++- .../strolch/persistence/api/StrolchDao.java | 234 ++++++++++- .../persistence/api/StrolchTransaction.java | 35 +- .../inmemory/InMemoryActivityDao.java | 4 + .../persistence/inmemory/InMemoryDao.java | 155 +++++-- .../inmemory/InMemoryOrderDao.java | 4 + .../inmemory/InMemoryPersistence.java | 7 +- .../inmemory/InMemoryResourceDao.java | 4 + .../strolch/agent/ComponentContainerTest.java | 1 + .../inmemory/InMemoryOrderQueryTest.java | 319 +++++++++++++++ .../inmemory/InMemoryPersistenceHandler.java | 5 +- ...st.java => InMemoryResourceQueryTest.java} | 103 ++--- .../config/StrolchConfiguration.xml | 2 +- .../realmtest/config/StrolchConfiguration.xml | 2 +- .../config/StrolchConfiguration.xml | 2 +- .../versioningtest/config/PrivilegeConfig.xml | 32 ++ .../versioningtest/config/PrivilegeRoles.xml | 16 + .../versioningtest/config/PrivilegeUsers.xml | 18 + .../config/StrolchConfiguration.xml | 46 +++ .../resources/versioningtest/data/Orders.xml | 14 + .../versioningtest/data/Resources.xml | 14 + .../versioningtest/data/StrolchModel.xml | 29 ++ .../main/java/li/strolch/model/Locator.java | 8 +- .../java/li/strolch/model/ModelGenerator.java | 27 ++ .../src/main/java/li/strolch/model/Order.java | 8 +- .../main/java/li/strolch/model/Resource.java | 9 +- .../li/strolch/model/StrolchRootElement.java | 13 +- .../main/java/li/strolch/model/Version.java | 94 ++++- .../li/strolch/model/activity/Activity.java | 10 +- .../model/visitor/ElementTypeVisitor.java | 6 + .../visitor/StrolchRootElementVisitor.java | 3 + .../postgresql/PostgreSqlActivityDao.java | 117 +++++- .../postgresql/PostgreSqlOrderDao.java | 106 ++++- .../postgresql/PostgreSqlQueryVisitor.java | 2 +- .../postgresql/PostgreSqlResourceDao.java | 112 +++++- .../PostgreSqlStrolchTransaction.java | 8 +- .../persistence/postgresql/PostgresqlDao.java | 106 +++-- .../postgresql/PostgresqlXmlDao.java | 14 - .../strolch_db_schema_0.5.0_drop.sql | 11 + .../strolch_db_schema_0.5.0_initial.sql | 131 ++++++ .../strolch_db_schema_0.5.0_migration.sql | 72 ++++ .../resources/strolch_db_version.properties | 2 +- .../config/StrolchConfiguration.xml | 2 +- .../strolch/persistence/xml/AbstractDao.java | 19 + .../strolch/command/RemoveOrderCommand.java | 11 +- .../command/RemoveResourceCommand.java | 11 +- .../command/UpdateOrderCollectionCommand.java | 24 +- .../strolch/command/UpdateOrderCommand.java | 18 +- .../UpdateResourceCollectionCommand.java | 24 +- .../command/UpdateResourceCommand.java | 16 +- .../parameter/AddParameterCommand.java | 9 +- .../parameter/RemoveParameterCommand.java | 9 +- .../parameter/SetParameterCommand.java | 11 +- .../visitor/UndoUpdateElementVisitor.java | 66 +++ .../command/visitor/UpdateElementVisitor.java | 21 +- .../command/AbstractRealmCommandTest.java | 45 ++- .../AddOrderCollectionCommandTest.java | 21 +- .../strolch/command/AddOrderCommandTest.java | 17 +- .../AddResourceCollectionCommandTest.java | 21 +- .../command/AddResourceCommandTest.java | 17 +- .../RemoveOrderCollectionCommandTest.java | 21 +- .../command/RemoveOrderCommandTest.java | 17 +- .../RemoveResourceCollectionCommandTest.java | 21 +- .../command/RemoveResourceCommandTest.java | 17 +- .../UpdateOrderCollectionCommandTest.java | 23 +- .../command/UpdateOrderCommandTest.java | 12 + .../UpdateResourceCollectionCommandTest.java | 22 +- .../command/UpdateResourceCommandTest.java | 12 + .../parameter/AddParameterCommandTest.java | 19 +- .../parameter/RemoveParameterCommandTest.java | 19 +- .../parameter/SetParameterCommandTest.java | 20 +- .../testbase/runtime/AbstractModelTest.java | 42 +- .../runtime/ActivityModelTestRunner.java | 276 +++++++++++++ .../runtime/OrderModelTestRunner.java | 3 + .../runtime/ResourceModelTestRunner.java | 2 + .../runtime/VersioningTestRunner.java | 195 +++++++++ 94 files changed, 3634 insertions(+), 606 deletions(-) create mode 100644 li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryTest.java rename li.strolch.agent/src/{main/java/li/strolch/persistence => test/java/li/strolch/runtime/query}/inmemory/InMemoryPersistenceHandler.java (94%) rename li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/{InMemoryQueryTest.java => InMemoryResourceQueryTest.java} (75%) create mode 100644 li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeRoles.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeUsers.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/config/StrolchConfiguration.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/data/Orders.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/data/Resources.xml create mode 100644 li.strolch.agent/src/test/resources/versioningtest/data/StrolchModel.xml delete mode 100644 li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlXmlDao.java create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_drop.sql create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_initial.sql create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_migration.sql create mode 100644 li.strolch.service/src/main/java/li/strolch/command/visitor/UndoUpdateElementVisitor.java create mode 100644 li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ActivityModelTestRunner.java create mode 100644 li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/VersioningTestRunner.java diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/ElementMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/ElementMap.java index 398ae3e9f..beaebd5a0 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/ElementMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/ElementMap.java @@ -23,33 +23,106 @@ import li.strolch.model.StrolchRootElement; import li.strolch.model.parameter.Parameter; import li.strolch.model.parameter.StringListParameter; import li.strolch.model.parameter.StringParameter; +import li.strolch.persistence.api.StrolchDao; +import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.runtime.StrolchConstants; /** + *

+ * The {@link ElementMap} is the entry point to the Strolch model. Any access to Strolch objects is done using the + * {@link ElementMap}. A concrete {@link ElementMap} instance uses the given {@link StrolchTransaction} to access the a + * {@link StrolchDao} and perform the relevant operation. + *

+ * + *

+ * Note: + *

+ * * @author Robert von Burg + * + * @param + * the object instance being managed */ public interface ElementMap { + /** + * Returns true if the underlying persistence layer has elements with the given type + * + * @param tx + * the open {@link StrolchTransaction} + * @param type + * the type of element to check for + * + * @return true if the underlying persistence layer has elements with the given type + */ public boolean hasType(StrolchTransaction tx, String type); + /** + * Returns true if the underlying persistence layer has an element with the given type and ID + * + * @param tx + * the open {@link StrolchTransaction} + * @param type + * the type of element to check for + * @param id + * the ID of the element to check for + * + * @return true if the underlying persistence layer has an element with the given type and ID + */ public boolean hasElement(StrolchTransaction tx, String type, String id); + /** + * Returns the number of elements regardless of type in the underlying persistence layer + * + * @param tx + * the open {@link StrolchTransaction} + * + * @return the number of elements regardless of type in the underlying persistence layer + */ public long querySize(StrolchTransaction tx); + /** + * Returns the number of elements of the given type in the underlying persistence layer + * + * @param tx + * the open {@link StrolchTransaction} + * @param type + * the type of element for which the number of elements is to be queried + * + * @return the number of elements of the given type in the underlying persistence layer + */ public long querySize(StrolchTransaction tx, String type); /** - * Returns the element with the type "Template" and the id = type + * Returns a copy of the element with the type "Template" and the id = type * * @param tx * the open {@link StrolchTransaction} * @param type * The template id to return + * * @return the template, or null if it does not exist */ public T getTemplate(StrolchTransaction tx, String type); + /** + * Returns a copy of the element with the type "Template" and the id = type + * + * @param tx + * the open {@link StrolchTransaction} + * @param type + * The template id to return + * @param assertExists + * if true, and element does not exist, then a {@link StrolchException} is thrown + * + * @return the template, or null if it does not exist + * + * @throws StrolchException + * if the template does not exist + */ + public T getTemplate(StrolchTransaction tx, String type, boolean assertExists) throws StrolchException; + /** * Retrieves the element with the given type and id, or null if it does not exist * @@ -64,6 +137,63 @@ public interface ElementMap { */ public T getBy(StrolchTransaction tx, String type, String id); + /** + * Retrieves the element with the given type and id, or null if it does not exist + * + * @param tx + * the open transaction + * @param type + * the type of the element to retrieve + * @param id + * the id of the element to retrieve + * @param assertExists + * if true, and element does not exist, then a {@link StrolchException} is thrown + * + * @return the element with the type and id, or null if it does not exist + * + * @throws StrolchException + * if the element does not exist + */ + public T getBy(StrolchTransaction tx, String type, String id, boolean assertExists) throws StrolchException; + + /** + * Retrieves the specific version of the element with the given type and id, or null if it does not exist + * + * @param tx + * the open transaction + * @param type + * the type of the element to retrieve + * @param id + * the id of the element to retrieve + * @param version + * the version to get + * + * @return the element with the type and id, or null if it does not exist + */ + public T getBy(StrolchTransaction tx, String type, String id, int version); + + /** + * Retrieves the specific version of the element with the given type and id, or null if it does not exist + * + * @param tx + * the open transaction + * @param type + * the type of the element to retrieve + * @param id + * the id of the element to retrieve + * @param version + * the version to get + * @param assertExists + * if true, and element does not exist, then a {@link StrolchException} is thrown + * + * @return the element with the type and id, or null if it does not exist + * + * @throws StrolchException + * if the element does not exist + */ + public T getBy(StrolchTransaction tx, String type, String id, int version, boolean assertExists) + throws StrolchException; + /** * Returns the element which is referenced by the given {@link StringParameter}. A reference {@link Parameter} must * have its interpretation set to the element type being referenced e.g. s @@ -105,29 +235,264 @@ public interface ElementMap { */ public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) throws StrolchException; + /** + * Queries and returns all the versions of the element with the given type and ID + * + * @param tx + * the {@link StrolchTransaction} instance + * @param type + * the type of the element to be queried + * @param id + * the id of the element to be queried + * + * @return all the versions of the element with the given type and ID + */ + public List getVersionsFor(StrolchTransaction tx, String type, String id); + + /** + * Returns all elements in the underlying persistence layer regardless of type + * + * @param tx + * the {@link StrolchTransaction} instance + * + * @return all elements in the underlying persistence layer regardless of type + */ public List getAllElements(StrolchTransaction tx); + /** + * Returns all elements in the underlying persistence layer of the given type + * + * @param tx + * the {@link StrolchTransaction} instance + * + * @param type + * the type of the elements to retrieve + * + * @return all elements in the underlying persistence layer of the given type + */ public List getElementsBy(StrolchTransaction tx, String type); + /** + * Returns all the types known in the underlying persistence layer + * + * @param tx + * the {@link StrolchTransaction} instance + * + * @return all the types known in the underlying persistence layer + */ public Set getTypes(StrolchTransaction tx); + /** + * Returns all keys/IDs of all elements in the underlying persistence layer, regardless of type + * + * @param tx + * the {@link StrolchTransaction} instance + * + * @return all keys/IDs of all elements in the underlying persistence layer, regardless of type + */ public Set getAllKeys(StrolchTransaction tx); + /** + * Returns all keys/IDs of all elements in the underlying persistence layer, of the given type + * + * @param tx + * the {@link StrolchTransaction} instance + * @param type + * the type of the element to retrieve the keys for + * + * @return all keys/IDs of all elements in the underlying persistence layer, of the given type + */ public Set getKeysBy(StrolchTransaction tx, String type); - public void add(StrolchTransaction tx, T element); + /** + * Adds the given element to the underlying persistence layer. The element may not already exist + * + * @param tx + * the {@link StrolchTransaction} instance + * @param element + * the element to add + * + * @throws StrolchPersistenceException + * if an element already exists with the same ID + */ + public void add(StrolchTransaction tx, T element) throws StrolchPersistenceException; - public void addAll(StrolchTransaction tx, List elements); + /** + * Adds the given elements to the underlying persistence layer. None of the elements may already exist + * + * @param tx + * the {@link StrolchTransaction} instance + * @param elements + * the elements to add + * + * @throws StrolchPersistenceException + * if an element already exists with the same ID + */ + public void addAll(StrolchTransaction tx, List elements) throws StrolchPersistenceException; - public T update(StrolchTransaction tx, T element); + /** + * Updates the existing element + * + * @param tx + * the {@link StrolchTransaction} instance + * @param element + * the element to update + * + * @return the replaced element + * + * @throws StrolchPersistenceException + * if the element does not exist + */ + public void update(StrolchTransaction tx, T element) throws StrolchPersistenceException; - public List updateAll(StrolchTransaction tx, List elements); + /** + * Updates all the existing elements + * + * @param tx + * the {@link StrolchTransaction} instance + * @param elements + * the elements to update + * + * @return the replaced elements + * + * @throws StrolchPersistenceException + * if any of the elements don't yet exist + */ + public void updateAll(StrolchTransaction tx, List elements) throws StrolchPersistenceException; - public void remove(StrolchTransaction tx, T element); + /** + * Removes the given element + * + * @param tx + * the {@link StrolchTransaction} instance + * @param element + * the element to be removed + * + * @throws StrolchPersistenceException + * if the element does not exist + */ + public void remove(StrolchTransaction tx, T element) throws StrolchPersistenceException; - public void removeAll(StrolchTransaction tx, List elements); + /** + * Removes the given elements + * + * @param tx + * the {@link StrolchTransaction} instance + * @param elements + * the elements to be removed + * + * @throws StrolchPersistenceException + * if any of the elements don't yet exist + */ + public void removeAll(StrolchTransaction tx, List elements) throws StrolchPersistenceException; + /** + *

+ * Removes all elements regardless of the type + *

+ * + *

+ * Note: This method ignores versioning. Do NOT call this method unless you want to clear the model! + *

+ * + * @param tx + * the {@link StrolchTransaction} instance + * + * @return the number of elements removed + */ public long removeAll(StrolchTransaction tx); + /** + *

+ * Removes all elements of the given type + *

+ * + *

+ * Note: This method ignores versioning. Do NOT call this method unless you want to clear the model! + *

+ * + * @param tx + * the {@link StrolchTransaction} instance + * @param type + * the type of elements to remove + * + * @return the number of elements removed + */ public long removeAllBy(StrolchTransaction tx, String type); + + /** + *

+ * Undoes the given version by reverting to the previous version of the element given. If the element given is the + * first version, then the element is removed. If the previous version exists, then it is reverted to it. If the + * given element does not exist, nor it's previous version, then a {@link StrolchPersistenceException} is thrown + *

+ * + *

+ * Note: This method should only be used in the same transaction where the element was created to undo a + * change in the same transaction. If there is a requirement to revert to a previous version, then the + * {@link #revertToVersion(StrolchTransaction, StrolchRootElement)} method. + *

+ * + * @param tx + * the {@link StrolchTransaction} instance + * @param element + * the element which is to be reverted to a previous version + * + * @throws StrolchException + * if the version does not exist, if this version is not the latest version, if the previous version is + * missing or other problems arise + */ + public void undoVersion(StrolchTransaction tx, T element) throws StrolchException; + + /** + *

+ * Reverts to the given version of the specified element. This method will retrieve the given version of the + * specified element, then set a new version on that element which is an increment of the current latest version and + * store this new version. The new element is then returned for convenience. + *

+ * + *

+ * Note: This is the method which should be called when a change should be reverted. This method gurantees + * that the history is not changed and that a new version is saved but with the version of the element specified. + *

+ * + * @param tx + * the {@link StrolchTransaction} instance + * @param element + * the version of the element to revert to + * + * @return the new version of the element + * + * @throws StrolchException + * if the version does not exist + */ + public T revertToVersion(StrolchTransaction tx, T element) throws StrolchException; + + /** + *

+ * Reverts to the given version of the specified element. This method will retrieve the given version of the + * specified element, then set a new version on that element which is an increment of the current latest version and + * store this new version. The new element is then returned for convenience. + *

+ * + *

+ * Note: This is the method which should be called when a change should be reverted. This method gurantees + * that the history is not changed and that a new version is saved but with the version of the element specified. + *

+ * + * @param tx + * the {@link StrolchTransaction} instance + * @param type + * the type of the element to revert to + * @param id + * the id of the element to revert to + * @param version + * the version of the specified element to revert to + * + * @return the new version of the element + * + * @throws StrolchException + * if the version does not exist + */ + public T revertToVersion(StrolchTransaction tx, String type, String id, int version) throws StrolchException; } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java index cd878bf14..5f92bf20c 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java @@ -45,5 +45,7 @@ public interface StrolchRealm { public boolean isUpdateObservers(); + public boolean isVersioningEnabled(); + public ObserverHandler getObserverHandler(); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java index 9df714f60..612e864b6 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java @@ -137,7 +137,15 @@ public class AuditingElementMapFacade implements E @Override public T getTemplate(StrolchTransaction tx, String type) { T template = this.elementMap.getTemplate(tx, type); - if (this.observeAccessReads) + if (this.observeAccessReads && template != null) + this.read.add(template); + return template; + } + + @Override + public T getTemplate(StrolchTransaction tx, String type, boolean assertExists) throws StrolchException { + T template = this.elementMap.getTemplate(tx, type, assertExists); + if (this.observeAccessReads && template != null) this.read.add(template); return template; } @@ -145,7 +153,32 @@ public class AuditingElementMapFacade implements E @Override public T getBy(StrolchTransaction tx, String type, String id) { T element = this.elementMap.getBy(tx, type, id); - if (this.observeAccessReads) + if (this.observeAccessReads && element != null) + this.read.add(element); + return element; + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, boolean assertExists) throws StrolchException { + T element = this.elementMap.getBy(tx, type, id, assertExists); + if (this.observeAccessReads && element != null) + this.read.add(element); + return element; + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version) { + T element = this.elementMap.getBy(tx, type, id, version); + if (this.observeAccessReads && element != null) + this.read.add(element); + return element; + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version, boolean assertExists) + throws StrolchException { + T element = this.elementMap.getBy(tx, type, id, version, assertExists); + if (this.observeAccessReads && element != null) this.read.add(element); return element; } @@ -153,23 +186,32 @@ public class AuditingElementMapFacade implements E @Override public T getBy(StrolchTransaction tx, StringParameter refP, boolean assertExists) throws StrolchException { T element = this.elementMap.getBy(tx, refP, assertExists); - if (this.observeAccessReads) + if (this.observeAccessReads && element != null) this.read.add(element); return element; } @Override - public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) throws StrolchException { + public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) + throws StrolchException { List elements = this.elementMap.getBy(tx, refP, assertExists); - if (this.observeAccessReads) + if (this.observeAccessReads && !elements.isEmpty()) this.read.addAll(elements); return elements; } + @Override + public List getVersionsFor(StrolchTransaction tx, String type, String id) { + List versions = this.elementMap.getVersionsFor(tx, type, id); + if (this.observeAccessReads && !versions.isEmpty()) + this.read.add(versions.get(versions.size() - 1)); + return versions; + } + @Override public List getAllElements(StrolchTransaction tx) { List elements = this.elementMap.getAllElements(tx); - if (this.observeAccessReads) + if (this.observeAccessReads && !elements.isEmpty()) this.read.addAll(elements); return elements; } @@ -177,7 +219,7 @@ public class AuditingElementMapFacade implements E @Override public List getElementsBy(StrolchTransaction tx, String type) { List elements = this.elementMap.getElementsBy(tx, type); - if (this.observeAccessReads) + if (this.observeAccessReads && !elements.isEmpty()) this.read.addAll(elements); return elements; } @@ -210,18 +252,15 @@ public class AuditingElementMapFacade implements E } @Override - public T update(StrolchTransaction tx, T element) { - T replaced = this.elementMap.update(tx, element); + public void update(StrolchTransaction tx, T element) { + this.elementMap.update(tx, element); this.updated.add(element); - return replaced; - } @Override - public List updateAll(StrolchTransaction tx, List elements) { - List replaced = this.elementMap.updateAll(tx, elements); + public void updateAll(StrolchTransaction tx, List elements) { + this.elementMap.updateAll(tx, elements); this.updated.addAll(elements); - return replaced; } @Override @@ -255,4 +294,27 @@ public class AuditingElementMapFacade implements E return removed; } + + @Override + public T revertToVersion(StrolchTransaction tx, String type, String id, int version) throws StrolchException { + T element = this.elementMap.revertToVersion(tx, type, id, version); + this.updated.add(element); + return element; + } + + @Override + public T revertToVersion(StrolchTransaction tx, T element) throws StrolchException { + T revertedElement = this.elementMap.revertToVersion(tx, element); + this.updated.add(revertedElement); + return revertedElement; + } + + @Override + public void undoVersion(StrolchTransaction tx, T element) throws StrolchException { + this.elementMap.undoVersion(tx, element); + if (element.getVersion().isFirstVersion()) + this.deleted.add(element); + else + this.updated.add(element); + } } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedActivityMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedActivityMap.java index 87e4510e8..ff0abea82 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedActivityMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedActivityMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_ACTIVITY_REF import java.util.List; import li.strolch.agent.api.ActivityMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.activity.Activity; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ActivityQuery; @@ -31,9 +32,10 @@ public class CachedActivityMap extends CachedElementMap implements Act private ActivityDao cachedDao; - public CachedActivityMap() { - super(); - this.cachedDao = new InMemoryActivityDao(); + public CachedActivityMap(StrolchRealm realm) { + super(realm); + // the cached DAO should not have versioning enabled + this.cachedDao = new InMemoryActivityDao(false); } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedElementMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedElementMap.java index b0c1c7e1f..8efdebe53 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedElementMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedElementMap.java @@ -16,23 +16,27 @@ package li.strolch.agent.impl; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import li.strolch.agent.api.ElementMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.exception.StrolchException; import li.strolch.model.StrolchRootElement; +import li.strolch.model.Version; import li.strolch.model.parameter.Parameter; import li.strolch.model.parameter.StringListParameter; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchDao; +import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.runtime.StrolchConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * @author Robert von Burg */ @@ -40,6 +44,16 @@ public abstract class CachedElementMap implements private static final Logger logger = LoggerFactory.getLogger(CachedElementMap.class); + private StrolchRealm realm; + + public CachedElementMap(StrolchRealm realm) { + this.realm = realm; + } + + protected StrolchRealm getRealm() { + return this.realm; + } + protected abstract StrolchDao getCachedDao(); protected abstract StrolchDao getDbDao(StrolchTransaction tx); @@ -66,62 +80,110 @@ public abstract class CachedElementMap implements @Override public synchronized T getTemplate(StrolchTransaction tx, String type) { - return getBy(tx, StrolchConstants.TEMPLATE, type); + return getTemplate(tx, type, false); + } + + @Override + public T getTemplate(StrolchTransaction tx, String type, boolean assertExists) { + T t = getCachedDao().queryBy(StrolchConstants.TEMPLATE, type); + if (assertExists && t == null) { + String msg = "The template for type {0} does not exist!"; //$NON-NLS-1$ + throw new StrolchException(MessageFormat.format(msg, type)); + } + + if (t == null) + return null; + + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; } @Override public synchronized T getBy(StrolchTransaction tx, String type, String id) { - return getCachedDao().queryBy(type, id); + return getBy(tx, type, id, false); } - protected abstract void assertIsRefParam(Parameter refP); + @Override + public T getBy(StrolchTransaction tx, String type, String id, boolean assertExists) throws StrolchException { + T t = getCachedDao().queryBy(type, id); + if (assertExists && t == null) { + String msg = "The element for type {0} and id {1} does not exist!"; //$NON-NLS-1$ + throw new StrolchException(MessageFormat.format(msg, type, id)); + } + + if (t == null) + return null; + + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version) { + return getBy(tx, type, id, version, false); + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version, boolean assertExists) + throws StrolchException { + T t = getDbDao(tx).queryBy(type, id, version); + if (assertExists && t == null) { + String msg = "The element for type {0} and id {1} and version {2} does not exist!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type, id, version); + throw new StrolchException(msg); + } + return t; + } @Override public T getBy(StrolchTransaction tx, StringParameter refP, boolean assertExists) throws StrolchException { assertIsRefParam(refP); String type = refP.getUom(); String id = refP.getValue(); - T element = getBy(tx, type, id); - if (assertExists && element == null) { - String msg = "The element for refP {0} with id {1} does not exist!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, refP.getLocator(), id); - throw new StrolchException(msg); - } - return element; + return getBy(tx, type, id, assertExists); } @Override - public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) throws StrolchException { + public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) + throws StrolchException { assertIsRefParam(refP); - List elements = new ArrayList<>(); String type = refP.getUom(); List ids = refP.getValue(); - for (String id : ids) { - T element = getBy(tx, type, id); - if (element != null) { - elements.add(element); - } else if (assertExists) { - if (assertExists && element == null) { - String msg = "The element for refP {0} with id {1} does not exist!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, refP.getLocator(), id); - throw new StrolchException(msg); - } - } - } + return ids.stream().map(id -> getBy(tx, type, id, assertExists)).filter(Objects::nonNull) + .collect(Collectors.toList()); + } - return elements; + @Override + public List getVersionsFor(StrolchTransaction tx, String type, String id) { + return getDbDao(tx).queryVersionsFor(type, id); } @Override public synchronized List getAllElements(StrolchTransaction tx) { - return getCachedDao().queryAll(); + List all = getCachedDao().queryAll(); + return all.stream().map(t -> { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + }).collect(Collectors.toList()); } @Override public synchronized List getElementsBy(StrolchTransaction tx, String type) { - return getCachedDao().queryAll(type); + List all = getCachedDao().queryAll(type); + return all.stream().map(t -> { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + }).collect(Collectors.toList()); } @Override @@ -139,14 +201,6 @@ public abstract class CachedElementMap implements return getCachedDao().queryKeySet(type); } - @Override - public synchronized void add(StrolchTransaction tx, T element) { - // first perform cached change - getCachedDao().save(element); - // last is to perform DB changes - getDbDao(tx).save(element); - } - /** * Special method used when starting the container to cache the values. Not to be used anywhere else but from the * {@link CachedRealm} @@ -158,27 +212,28 @@ public abstract class CachedElementMap implements getCachedDao().save(element); } - // TODO for update we should return the updated elements, or remove the return value - @Override - public synchronized T update(StrolchTransaction tx, T element) { + public synchronized void add(StrolchTransaction tx, T element) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + // first perform cached change - getCachedDao().update(element); + getCachedDao().save(element); // last is to perform DB changes - getDbDao(tx).update(element); - - return element; - } - - @Override - public synchronized void remove(StrolchTransaction tx, T element) { - // first perform cached change - getCachedDao().remove(element); - getDbDao(tx).remove(element); + getDbDao(tx).save(element); } @Override public synchronized void addAll(StrolchTransaction tx, List elements) { + for (T element : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + } + // first perform cached change getCachedDao().saveAll(elements); // last is to perform DB changes @@ -186,21 +241,79 @@ public abstract class CachedElementMap implements } @Override - public synchronized List updateAll(StrolchTransaction tx, List elements) { + public synchronized void update(StrolchTransaction tx, T element) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + + // first perform cached change + getCachedDao().update(element); + // last is to perform DB changes + getDbDao(tx).update(element); + } + + @Override + public synchronized void updateAll(StrolchTransaction tx, List elements) { + for (T t : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(t, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(t, tx.getCertificate().getUsername()); + } + // first perform cached change getCachedDao().updateAll(elements); // last is to perform DB changes getDbDao(tx).updateAll(elements); + } - return elements; + @Override + public synchronized void remove(StrolchTransaction tx, T element) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), true); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + + if (this.realm.isVersioningEnabled()) { + + // first perform cached change + getCachedDao().update(element); + // last is to perform DB changes + getDbDao(tx).update(element); + + } else { + + // first perform cached change + getCachedDao().remove(element); + // last is to perform DB changes + getDbDao(tx).remove(element); + } } @Override public synchronized void removeAll(StrolchTransaction tx, List elements) { - // first perform cached change - getCachedDao().removeAll(elements); - // last is to perform DB changes - getDbDao(tx).removeAll(elements); + for (T t : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(t, tx.getCertificate().getUsername(), true); + else + Version.setInitialVersionFor(t, tx.getCertificate().getUsername()); + } + + if (this.realm.isVersioningEnabled()) { + + // first perform cached change + getCachedDao().updateAll(elements); + // last is to perform DB changes + getDbDao(tx).updateAll(elements); + + } else { + + // first perform cached change + getCachedDao().removeAll(elements); + // last is to perform DB changes + getDbDao(tx).removeAll(elements); + } } @Override @@ -232,4 +345,63 @@ public abstract class CachedElementMap implements return removed; } + + @Override + public T revertToVersion(StrolchTransaction tx, T element) throws StrolchException { + return revertToVersion(tx, element.getType(), element.getId(), element.getVersion().getVersion()); + } + + @Override + public T revertToVersion(StrolchTransaction tx, String type, String id, int version) throws StrolchException { + if (!this.realm.isVersioningEnabled()) { + throw new StrolchPersistenceException("Can not und a version if versioning is not enabled!"); + } + + // get the current and specified version + T current = getBy(tx, type, id, true); + T versionT = getBy(tx, type, id, version, true); + + // create the new version + @SuppressWarnings("unchecked") + T clone = (T) versionT.getClone(); + clone.setVersion(current.getVersion().next(tx.getCertificate().getUsername(), false)); + + // save the new version + getCachedDao().update(clone); + getDbDao(tx).update(clone); + + // and return new version + return clone; + } + + @Override + public void undoVersion(StrolchTransaction tx, T element) throws StrolchException { + if (!this.realm.isVersioningEnabled()) { + throw new StrolchPersistenceException("Can not und a version if versioning is not enabled!"); + } + + String type = element.getType(); + String id = element.getId(); + + Version elementVersion = element.getVersion(); + + // make sure the given element is the latest version + T current = getBy(tx, type, id, true); + if (!current.getVersion().equals(elementVersion)) { + String msg = "Can not undo the version {0} as it is not the latest!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, elementVersion); + throw new StrolchException(msg); + } + + if (elementVersion.isFirstVersion()) { + getCachedDao().remove(element); + getDbDao(tx).remove(element); + } else { + T previous = getBy(tx, type, id, elementVersion.getPreviousVersion(), false); + getCachedDao().update(previous); + getDbDao(tx).removeVersion(current); + } + } + + protected abstract void assertIsRefParam(Parameter refP); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedOrderMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedOrderMap.java index 45dfeb3ad..2b277e979 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedOrderMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedOrderMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_ORDER_REF; import java.util.List; import li.strolch.agent.api.OrderMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Order; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.OrderQuery; @@ -31,9 +32,10 @@ public class CachedOrderMap extends CachedElementMap implements OrderMap private OrderDao cachedDao; - public CachedOrderMap() { - super(); - this.cachedDao = new InMemoryOrderDao(); + public CachedOrderMap(StrolchRealm realm) { + super(realm); + // the cached DAO should not have versioning enabled + this.cachedDao = new InMemoryOrderDao(false); } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java index b16d3afd8..a48ceab8f 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java @@ -95,17 +95,14 @@ public class CachedRealm extends InternalStrolchRealm { super.initialize(container, configuration); this.persistenceHandler = container.getComponent(PersistenceHandler.class); - this.resourceMap = new CachedResourceMap(); - this.orderMap = new CachedOrderMap(); - this.activityMap = new CachedActivityMap(); + this.resourceMap = new CachedResourceMap(this); + this.orderMap = new CachedOrderMap(this); + this.activityMap = new CachedActivityMap(this); - if (isAuditTrailEnabled()) { + if (isAuditTrailEnabled()) this.auditTrail = new CachedAuditTrail(); - logger.info("Enabling AuditTrail for realm " + getRealm()); //$NON-NLS-1$ - } else { + else this.auditTrail = new NoStrategyAuditTrail(); - logger.info("AuditTrail is disabled for realm " + getRealm()); //$NON-NLS-1$ - } } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedResourceMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedResourceMap.java index df6660257..a0592a104 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedResourceMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedResourceMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_RESOURCE_REF import java.util.List; import li.strolch.agent.api.ResourceMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Resource; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ResourceQuery; @@ -31,9 +32,10 @@ public class CachedResourceMap extends CachedElementMap implements Res private ResourceDao cachedDao; - public CachedResourceMap() { - super(); - this.cachedDao = new InMemoryResourceDao(); + public CachedResourceMap(StrolchRealm realm) { + super(realm); + // the cached DAO should not have versioning enabled + this.cachedDao = new InMemoryResourceDao(false); } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java index a1f85c615..fe9deca2e 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java @@ -39,6 +39,7 @@ public class DefaultRealmHandler extends StrolchComponent implements RealmHandle public static final String PROP_ENABLE_AUDIT_TRAIL = "enableAuditTrail"; //$NON-NLS-1$ public static final String PROP_ENABLE_AUDIT_TRAIL_FOR_READ = "enableAuditTrailForRead"; //$NON-NLS-1$ public static final String PROP_ENABLE_OBSERVER_UPDATES = "enableObserverUpdates"; //$NON-NLS-1$ + public static final String PROP_ENABLE_VERSIONING = "enableVersioning"; //$NON-NLS-1$ public static final String PREFIX_DATA_STORE_MODE = "dataStoreMode"; //$NON-NLS-1$ public static final String PREFIX_DATA_STORE_FILE = "dataStoreFile"; //$NON-NLS-1$ public static final String PROP_REALMS = "realms"; //$NON-NLS-1$ @@ -76,9 +77,11 @@ public class DefaultRealmHandler extends StrolchComponent implements RealmHandle this.realms = new HashMap<>(); String[] realms = configuration.getStringArray(PROP_REALMS, StrolchConstants.DEFAULT_REALM); for (String realmName : realms) { + String dataStoreModeKey = StrolchConstants.makeRealmKey(realmName, PREFIX_DATA_STORE_MODE); String realmMode = configuration.getString(dataStoreModeKey, null); DataStoreMode dataStoreMode = DataStoreMode.parseDataStoreMode(realmMode); + InternalStrolchRealm realm = dataStoreMode.createRealm(realmName); this.realms.put(realmName, realm); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java index 17a70e1b1..854cc9f3d 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java @@ -27,7 +27,6 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.inmemory.InMemoryPersistence; import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.PrivilegeContext; -import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.utils.dbc.DBC; @@ -86,18 +85,15 @@ public class EmptyRealm extends InternalStrolchRealm { @Override public void initialize(ComponentContainer container, ComponentConfiguration configuration) { super.initialize(container, configuration); - this.persistenceHandler = new InMemoryPersistence(container.getPrivilegeHandler()); - this.resourceMap = new TransactionalResourceMap(); - this.orderMap = new TransactionalOrderMap(); + this.persistenceHandler = new InMemoryPersistence(container.getPrivilegeHandler(), isVersioningEnabled()); + this.resourceMap = new TransactionalResourceMap(this); + this.orderMap = new TransactionalOrderMap(this); + this.activityMap = new TransactionalActivityMap(this); - String enableAuditKey = StrolchConstants.makeRealmKey(getRealm(), DefaultRealmHandler.PROP_ENABLE_AUDIT_TRAIL); - if (configuration.getBoolean(enableAuditKey, Boolean.FALSE)) { + if (isAuditTrailEnabled()) this.auditTrail = new TransactionalAuditTrail(); - logger.info("Enabling AuditTrail for realm " + getRealm()); //$NON-NLS-1$ - } else { + else this.auditTrail = new NoStrategyAuditTrail(); - logger.info("AuditTrail is disabled for realm " + getRealm()); //$NON-NLS-1$ - } } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java index cd02a7143..62801dfe7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java @@ -15,9 +15,17 @@ */ package li.strolch.agent.impl; +import static li.strolch.agent.impl.DefaultRealmHandler.PROP_ENABLE_AUDIT_TRAIL; +import static li.strolch.agent.impl.DefaultRealmHandler.PROP_ENABLE_AUDIT_TRAIL_FOR_READ; +import static li.strolch.agent.impl.DefaultRealmHandler.PROP_ENABLE_OBSERVER_UPDATES; +import static li.strolch.agent.impl.DefaultRealmHandler.PROP_ENABLE_VERSIONING; + import java.text.MessageFormat; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.ComponentContainer; @@ -32,9 +40,6 @@ import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.utils.dbc.DBC; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * @author Robert von Burg */ @@ -47,6 +52,7 @@ public abstract class InternalStrolchRealm implements StrolchRealm { private LockHandler lockHandler; private boolean auditTrailEnabled; private boolean auditTrailEnabledForRead; + private boolean versioningEnabled; private boolean updateObservers; private ObserverHandler observerHandler; @@ -78,26 +84,51 @@ public abstract class InternalStrolchRealm implements StrolchRealm { public void initialize(ComponentContainer container, ComponentConfiguration configuration) { - String propTryLockTimeUnit = StrolchConstants.makeRealmKey(this.realm, PROP_TRY_LOCK_TIME_UNIT); - String propTryLockTime = StrolchConstants.makeRealmKey(this.realm, PROP_TRY_LOCK_TIME); + logger.info("Initializing Realm " + getRealm() + "..."); - String enableAuditKey = StrolchConstants.makeRealmKey(getRealm(), DefaultRealmHandler.PROP_ENABLE_AUDIT_TRAIL); + // audits + String enableAuditKey = StrolchConstants.makeRealmKey(getRealm(), PROP_ENABLE_AUDIT_TRAIL); this.auditTrailEnabled = configuration.getBoolean(enableAuditKey, Boolean.FALSE); - String enableAuditForReadKey = StrolchConstants.makeRealmKey(getRealm(), - DefaultRealmHandler.PROP_ENABLE_AUDIT_TRAIL_FOR_READ); + // audits for read + String enableAuditForReadKey = StrolchConstants.makeRealmKey(getRealm(), PROP_ENABLE_AUDIT_TRAIL_FOR_READ); this.auditTrailEnabledForRead = configuration.getBoolean(enableAuditForReadKey, Boolean.FALSE); - String updateObserversKey = StrolchConstants.makeRealmKey(getRealm(), - DefaultRealmHandler.PROP_ENABLE_OBSERVER_UPDATES); + // observer updates + String updateObserversKey = StrolchConstants.makeRealmKey(getRealm(), PROP_ENABLE_OBSERVER_UPDATES); this.updateObservers = configuration.getBoolean(updateObserversKey, Boolean.FALSE); if (this.updateObservers) this.observerHandler = new DefaultObserverHandler(); + // lock timeout + String propTryLockTimeUnit = StrolchConstants.makeRealmKey(this.realm, PROP_TRY_LOCK_TIME_UNIT); + String propTryLockTime = StrolchConstants.makeRealmKey(this.realm, PROP_TRY_LOCK_TIME); TimeUnit timeUnit = TimeUnit.valueOf(configuration.getString(propTryLockTimeUnit, TimeUnit.SECONDS.name())); long time = configuration.getLong(propTryLockTime, 10); - logger.info(MessageFormat.format("Using a locking try timeout of {0}s", timeUnit.toSeconds(time))); //$NON-NLS-1$ this.lockHandler = new DefaultLockHandler(this.realm, timeUnit, time); + + // versioning + String enableVersioningKey = StrolchConstants.makeRealmKey(getRealm(), PROP_ENABLE_VERSIONING); + this.versioningEnabled = configuration.getBoolean(enableVersioningKey, Boolean.FALSE); + + if (this.auditTrailEnabled) + logger.info("Enabling AuditTrail for realm " + getRealm()); //$NON-NLS-1$ + else + logger.info("AuditTrail not enabled for realm " + getRealm()); //$NON-NLS-1$ + if (this.auditTrailEnabledForRead) + logger.info("Enabling AuditTrail for read for realm " + getRealm()); //$NON-NLS-1$ + else + logger.info("AuditTrail not enabled for read for realm " + getRealm()); //$NON-NLS-1$ + if (this.updateObservers) + logger.info("Enabling Observer Updates for realm " + getRealm()); //$NON-NLS-1$ + else + logger.info("Observer Updates not enabled for realm " + getRealm()); //$NON-NLS-1$ + if (this.versioningEnabled) + logger.info("Enabling Versioning for realm " + getRealm()); //$NON-NLS-1$ + else + logger.info("Versioning not enabled for realm " + getRealm()); //$NON-NLS-1$ + + logger.info(MessageFormat.format("Using a locking try timeout of {0}s", timeUnit.toSeconds(time))); //$NON-NLS-1$ } @Override @@ -115,6 +146,11 @@ public abstract class InternalStrolchRealm implements StrolchRealm { return this.updateObservers; } + @Override + public boolean isVersioningEnabled() { + return this.versioningEnabled; + } + @Override public ObserverHandler getObserverHandler() { if (!this.updateObservers) diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalActivityMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalActivityMap.java index fee085e4c..a2dce8cd7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalActivityMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalActivityMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_ACTIVITY_REF import java.util.List; import li.strolch.agent.api.ActivityMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.activity.Activity; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ActivityQuery; @@ -28,6 +29,10 @@ import li.strolch.persistence.api.StrolchTransaction; public class TransactionalActivityMap extends TransactionalElementMap implements ActivityMap { + public TransactionalActivityMap(StrolchRealm realm) { + super(realm); + } + @Override protected void assertIsRefParam(Parameter refP) { ElementMapHelpers.assertIsRefParam(INTERPRETATION_ACTIVITY_REF, refP); diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java index 503246c03..8b04ea217 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java @@ -63,7 +63,7 @@ public class TransactionalAuditTrail implements AuditTrail { public Audit getBy(StrolchTransaction tx, String type, Long id) { return getDao(tx).queryBy(type, id); } - + @Override public List getAllElements(StrolchTransaction tx, String type, DateRange dateRange) { return getDao(tx).queryAll(type, dateRange); diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalElementMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalElementMap.java index 67f4c7ed3..efe7833e6 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalElementMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalElementMap.java @@ -16,17 +16,21 @@ package li.strolch.agent.impl; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import li.strolch.agent.api.ElementMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.exception.StrolchException; import li.strolch.model.StrolchRootElement; +import li.strolch.model.Version; import li.strolch.model.parameter.Parameter; import li.strolch.model.parameter.StringListParameter; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchDao; +import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.runtime.StrolchConstants; @@ -37,6 +41,16 @@ import li.strolch.runtime.StrolchConstants; */ public abstract class TransactionalElementMap implements ElementMap { + private StrolchRealm realm; + + public TransactionalElementMap(StrolchRealm realm) { + this.realm = realm; + } + + protected StrolchRealm getRealm() { + return this.realm; + } + protected abstract StrolchDao getDao(StrolchTransaction tx); @Override @@ -61,62 +75,160 @@ public abstract class TransactionalElementMap impl @Override public T getTemplate(StrolchTransaction tx, String type) { - return getBy(tx, StrolchConstants.TEMPLATE, type); + return getTemplate(tx, type, false); + } + + @Override + public T getTemplate(StrolchTransaction tx, String type, boolean assertExists) throws StrolchException { + T t = getBy(tx, StrolchConstants.TEMPLATE, type); + if (assertExists && t == null) { + String msg = "The template for type {0} does not exist!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type); + throw new StrolchException(msg); + } + + if (t == null) + return null; + + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; } @Override public T getBy(StrolchTransaction tx, String type, String id) { - return getDao(tx).queryBy(type, id); + return getBy(tx, type, id, false); } - protected abstract void assertIsRefParam(Parameter refP); + @Override + public T getBy(StrolchTransaction tx, String type, String id, boolean assertExists) throws StrolchException { + T t = getDao(tx).queryBy(type, id); + if (assertExists && t == null) { + String msg = "The element for type {0} and id {1} does not exist!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type, id); + throw new StrolchException(msg); + } + + if (t == null) + return null; + + if (!this.realm.getMode().isTransient()) + return t; + + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version) { + return getBy(tx, type, id, version, false); + } + + @Override + public T getBy(StrolchTransaction tx, String type, String id, int version, boolean assertExists) + throws StrolchException { + T t = getDao(tx).queryBy(type, id, version); + if (assertExists && t == null) { + String msg = "The element for type {0} and id {1} and version {2} does not exist!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type, id, version); + throw new StrolchException(msg); + } + return t; + } @Override public T getBy(StrolchTransaction tx, StringParameter refP, boolean assertExists) throws StrolchException { assertIsRefParam(refP); String type = refP.getUom(); String id = refP.getValue(); - T element = getBy(tx, type, id); - if (assertExists && element == null) { + T t = getBy(tx, type, id); + if (assertExists && t == null) { String msg = "The element for refP {0} with id {1} does not exist!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, refP.getLocator(), id); throw new StrolchException(msg); } - return element; + + if (t == null) + return null; + + if (!this.realm.getMode().isTransient()) + return t; + + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; } @Override - public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) throws StrolchException { + public List getBy(StrolchTransaction tx, StringListParameter refP, boolean assertExists) + throws StrolchException { assertIsRefParam(refP); - List elements = new ArrayList<>(); String type = refP.getUom(); List ids = refP.getValue(); - for (String id : ids) { - T element = getBy(tx, type, id); - if (element != null) { - elements.add(element); - } else if (assertExists) { - if (assertExists && element == null) { - String msg = "The element for refP {0} with id {1} does not exist!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, refP.getLocator(), id); - throw new StrolchException(msg); - } + return ids.stream().map(id -> { + T t = getBy(tx, type, id); + if (assertExists && t == null) { + String msg = "The element for refP {0} with id {1} does not exist!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, refP.getLocator(), id); + throw new StrolchException(msg); } - } + return t; + }).filter(Objects::nonNull).map(t -> { + if (!this.realm.getMode().isTransient()) { + return t; + } else { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + } + }).collect(Collectors.toList()); + } - return elements; + @Override + public List getVersionsFor(StrolchTransaction tx, String type, String id) { + return getDao(tx).queryVersionsFor(type, id).stream().map(t -> { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + }).collect(Collectors.toList()); } @Override public List getAllElements(StrolchTransaction tx) { - return getDao(tx).queryAll(); + List all = getDao(tx).queryAll(); + + if (this.realm.getMode().isTransient()) + return all; + + return all.stream().map(t -> { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + }).collect(Collectors.toList()); } @Override public List getElementsBy(StrolchTransaction tx, String type) { - return getDao(tx).queryAll(type); + List all = getDao(tx).queryAll(type); + + if (this.realm.getMode().isTransient()) + return all; + + return all.stream().map(t -> { + @SuppressWarnings("unchecked") + T clone = (T) t.getClone(); + clone.setVersion(t.getVersion()); + return clone; + }).collect(Collectors.toList()); } @Override @@ -136,34 +248,73 @@ public abstract class TransactionalElementMap impl @Override public void add(StrolchTransaction tx, T element) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + getDao(tx).save(element); } - @Override - public T update(StrolchTransaction tx, T element) { - getDao(tx).update(element); - return element; - } - - @Override - public void remove(StrolchTransaction tx, T element) { - getDao(tx).remove(element); - } - @Override public void addAll(StrolchTransaction tx, List elements) { + for (T element : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + } + getDao(tx).saveAll(elements); } @Override - public List updateAll(StrolchTransaction tx, List elements) { + public void update(StrolchTransaction tx, T element) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + + getDao(tx).update(element); + } + + @Override + public void updateAll(StrolchTransaction tx, List elements) { + for (T element : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), false); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + } + getDao(tx).updateAll(elements); - return elements; + } + + @Override + public void remove(StrolchTransaction tx, T element) { + if (this.realm.isVersioningEnabled()) { + Version.updateVersionFor(element, tx.getCertificate().getUsername(), true); + getDao(tx).update(element); + } else { + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + getDao(tx).remove(element); + } } @Override public void removeAll(StrolchTransaction tx, List elements) { - getDao(tx).removeAll(elements); + for (T element : elements) { + if (realm.isVersioningEnabled()) + Version.updateVersionFor(element, tx.getCertificate().getUsername(), true); + else + Version.setInitialVersionFor(element, tx.getCertificate().getUsername()); + } + + if (this.realm.isVersioningEnabled()) { + getDao(tx).updateAll(elements); + } else { + getDao(tx).removeAll(elements); + } } @Override @@ -175,4 +326,50 @@ public abstract class TransactionalElementMap impl public long removeAllBy(StrolchTransaction tx, String type) { return getDao(tx).removeAllBy(type); } + + @Override + public T revertToVersion(StrolchTransaction tx, T element) throws StrolchException { + return revertToVersion(tx, element.getType(), element.getId(), element.getVersion().getVersion()); + } + + @Override + public T revertToVersion(StrolchTransaction tx, String type, String id, int version) throws StrolchException { + if (!this.realm.isVersioningEnabled()) { + throw new StrolchPersistenceException("Can not revert to a version if versioning is not enabled!"); + } + + // get the current and specified version + T current = getBy(tx, type, id, true); + T versionT = getBy(tx, type, id, version, true); + + // create the new version + @SuppressWarnings("unchecked") + T clone = (T) versionT.getClone(); + clone.setVersion(current.getVersion().next(tx.getCertificate().getUsername(), false)); + + // save the new version + getDao(tx).update(clone); + + // and return new version + return clone; + } + + @Override + public void undoVersion(StrolchTransaction tx, T element) throws StrolchException { + if (!this.realm.isVersioningEnabled()) { + throw new StrolchPersistenceException("Can not undo a version if versioning is not enabled!"); + } + + // make sure the given element is the latest version + T current = getBy(tx, element.getType(), element.getId(), true); + if (!current.getVersion().equals(element.getVersion())) { + String msg = "Can not undo the version {0} as it is not the latest {1}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, element.getVersion(), current.getVersion()); + throw new StrolchException(msg); + } + + getDao(tx).removeVersion(current); + } + + protected abstract void assertIsRefParam(Parameter refP); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalOrderMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalOrderMap.java index 4d4d8cfbf..7c32880ff 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalOrderMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalOrderMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_ORDER_REF; import java.util.List; import li.strolch.agent.api.OrderMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Order; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.OrderQuery; @@ -28,6 +29,10 @@ import li.strolch.persistence.api.StrolchTransaction; public class TransactionalOrderMap extends TransactionalElementMap implements OrderMap { + public TransactionalOrderMap(StrolchRealm realm) { + super(realm); + } + @Override protected void assertIsRefParam(Parameter refP) { ElementMapHelpers.assertIsRefParam(INTERPRETATION_ORDER_REF, refP); diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java index 36cce26b0..28d9cbe28 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java @@ -85,16 +85,14 @@ public class TransactionalRealm extends InternalStrolchRealm { @Override public void initialize(ComponentContainer container, ComponentConfiguration configuration) { super.initialize(container, configuration); - this.resourceMap = new TransactionalResourceMap(); - this.orderMap = new TransactionalOrderMap(); - this.activityMap = new TransactionalActivityMap(); + this.resourceMap = new TransactionalResourceMap(this); + this.orderMap = new TransactionalOrderMap(this); + this.activityMap = new TransactionalActivityMap(this); if (isAuditTrailEnabled()) { this.auditTrail = new TransactionalAuditTrail(); - logger.info("Enabling AuditTrail for realm " + getRealm()); //$NON-NLS-1$ } else { this.auditTrail = new NoStrategyAuditTrail(); - logger.info("AuditTrail is disabled for realm " + getRealm()); //$NON-NLS-1$ } this.persistenceHandler = container.getComponent(PersistenceHandler.class); @@ -122,8 +120,8 @@ public class TransactionalRealm extends InternalStrolchRealm { long duration = System.nanoTime() - start; String durationS = StringHelper.formatNanoDuration(duration); - logger.info(MessageFormat.format( - "Initialized Transactional Maps for realm {0} took {1}.", getRealm(), durationS)); //$NON-NLS-1$ + logger.info( + MessageFormat.format("Initialized Transactional Maps for realm {0} took {1}.", getRealm(), durationS)); //$NON-NLS-1$ logger.info(MessageFormat.format("There are {0} Orders", nrOfOrders)); //$NON-NLS-1$ logger.info(MessageFormat.format("There are {0} Resources", nrOfResources)); //$NON-NLS-1$ logger.info(MessageFormat.format("There are {0} Activities", nrOfActivities)); //$NON-NLS-1$ diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalResourceMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalResourceMap.java index 700923aee..1dd644b94 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalResourceMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalResourceMap.java @@ -20,6 +20,7 @@ import static li.strolch.model.StrolchModelConstants.INTERPRETATION_RESOURCE_REF import java.util.List; import li.strolch.agent.api.ResourceMap; +import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Resource; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ResourceQuery; @@ -28,6 +29,10 @@ import li.strolch.persistence.api.StrolchTransaction; public class TransactionalResourceMap extends TransactionalElementMap implements ResourceMap { + public TransactionalResourceMap(StrolchRealm realm) { + super(realm); + } + @Override protected void assertIsRefParam(Parameter refP) { ElementMapHelpers.assertIsRefParam(INTERPRETATION_RESOURCE_REF, refP); diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java index cfd6478a4..c4d521abc 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java @@ -103,18 +103,15 @@ public class TransientRealm extends InternalStrolchRealm { this.modelFile = configuration.getDataFile(key, null, configuration.getRuntimeConfiguration(), true); - this.persistenceHandler = new InMemoryPersistence(container.getPrivilegeHandler()); - this.resourceMap = new TransactionalResourceMap(); - this.orderMap = new TransactionalOrderMap(); - this.activityMap = new TransactionalActivityMap(); + this.persistenceHandler = new InMemoryPersistence(container.getPrivilegeHandler(), isVersioningEnabled()); + this.resourceMap = new TransactionalResourceMap(this); + this.orderMap = new TransactionalOrderMap(this); + this.activityMap = new TransactionalActivityMap(this); - if (isAuditTrailEnabled()) { + if (isAuditTrailEnabled()) this.auditTrail = new TransactionalAuditTrail(); - logger.info("Enabling AuditTrail for realm " + getRealm()); //$NON-NLS-1$ - } else { + else this.auditTrail = new NoStrategyAuditTrail(); - logger.info("AuditTrail is disabled for realm " + getRealm()); //$NON-NLS-1$ - } } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index 7ff89af30..51a3e0139 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -49,7 +49,6 @@ import li.strolch.model.Resource; import li.strolch.model.StrolchElement; import li.strolch.model.StrolchRootElement; import li.strolch.model.Tags; -import li.strolch.model.Version; import li.strolch.model.activity.Activity; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; @@ -67,7 +66,6 @@ import li.strolch.model.visitor.ElementTypeVisitor; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.PrivilegeContext; -import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.service.api.Command; import li.strolch.utils.dbc.DBC; @@ -86,7 +84,6 @@ public abstract class AbstractTransaction implements StrolchTransaction { private boolean suppressUpdates; private boolean suppressAudits; private boolean suppressDoNothingLogging; - private boolean versioningEnabled; private TransactionResult txResult; private List commands; @@ -147,7 +144,7 @@ public abstract class AbstractTransaction implements StrolchTransaction { return this.realm.getRealm(); } - protected StrolchRealm getRealm() { + public StrolchRealm getRealm() { return this.realm; } @@ -211,16 +208,6 @@ public abstract class AbstractTransaction implements StrolchTransaction { return this.suppressAudits; } - @Override - public void setVersioningEnabled(boolean versioningEnabled) { - this.versioningEnabled = versioningEnabled; - } - - @Override - public boolean isVersioningEnabled() { - return this.versioningEnabled; - } - @Override public boolean isSuppressDoNothingLogging() { return suppressDoNothingLogging; @@ -231,6 +218,11 @@ public abstract class AbstractTransaction implements StrolchTransaction { this.suppressDoNothingLogging = quietDoNothing; } + @Override + public boolean isVersioningEnabled() { + return this.realm.isVersioningEnabled(); + } + @Override public void lock(T element) throws StrolchLockException { this.realm.lock(element); @@ -407,22 +399,22 @@ public abstract class AbstractTransaction implements StrolchTransaction { @Override public Resource getResourceTemplate(String type) { - return getResourceBy(StrolchConstants.TEMPLATE, type); + return getResourceMap().getTemplate(this, type); } @Override public Resource getResourceTemplate(String type, boolean assertExists) throws StrolchException { - return getResourceBy(StrolchConstants.TEMPLATE, type, assertExists); + return getResourceMap().getTemplate(this, type, assertExists); } @Override public Order getOrderTemplate(String type) { - return getOrderBy(StrolchConstants.TEMPLATE, type); + return getOrderMap().getTemplate(this, type); } @Override public Order getOrderTemplate(String type, boolean assertExists) throws StrolchException { - return getOrderBy(StrolchConstants.TEMPLATE, type, assertExists); + return getOrderMap().getTemplate(this, type, assertExists); } @Override @@ -432,12 +424,7 @@ public abstract class AbstractTransaction implements StrolchTransaction { @Override public Order getOrderBy(String type, String id, boolean assertExists) throws StrolchException { - Order order = getOrderMap().getBy(this, type, id); - if (assertExists && order == null) { - String msg = "No Order exists with the id {0} with type {1}"; - throw new StrolchException(MessageFormat.format(msg, id, type)); - } - return order; + return getOrderMap().getBy(this, type, id, assertExists); } @Override @@ -580,21 +567,25 @@ public abstract class AbstractTransaction implements StrolchTransaction { } catch (Exception e) { this.txResult.setState(TransactionState.ROLLING_BACK); + try { undoCommands(); - } catch (Exception e2) { + } catch (Exception ex) { + logger.error("Failed to commit transaction and then undo commands due to " + ex.getMessage(), ex); try { rollback(this.txResult); handleRollback(start); - } catch (Exception e1) { - logger.error("Failed to roll back after failing to undo commands: " + e1.getMessage(), e1); //$NON-NLS-1$ + } catch (Exception exc) { + logger.error("Failed to roll back after failing to undo commands: " + exc.getMessage(), exc); //$NON-NLS-1$ } - handleFailure(start, e); + handleFailure(start, ex); } + try { rollback(this.txResult); handleRollback(start); - } catch (Exception e1) { + } catch (Exception ex) { + logger.error("Failed to commit transaction and then rollback due to " + ex.getMessage(), ex); handleFailure(start, e); } @@ -843,6 +834,8 @@ public abstract class AbstractTransaction implements StrolchTransaction { List audits = new ArrayList<>(); + // this is bad... doesn't account for a created and deleted in same TX... + if (this.orderMap != null) { if (this.realm.isAuditTrailEnabledForRead()) auditsFor(audits, AccessType.READ, Tags.ORDER, this.orderMap.getRead()); @@ -916,15 +909,6 @@ public abstract class AbstractTransaction implements StrolchTransaction { return audit; } - @Override - public void updateVersionFor(StrolchRootElement element, boolean deleted) { - if (this.versioningEnabled) { - int v = element.getVersion() == null ? 0 : element.getVersion().getVersion() + 1; - Version version = new Version(element.getLocator(), v, this.certificate.getUsername(), deleted); - element.setVersion(version); - } - } - /** * Calls {@link Command#validate()} on all registered command. This is done before we perform any commands and thus * no rollback needs be done due to invalid input for a command @@ -943,8 +927,8 @@ public abstract class AbstractTransaction implements StrolchTransaction { ListIterator iter = this.commands.listIterator(); while (iter.hasNext()) { Command command = iter.next(); - command.doCommand(); this.flushedCommands.add(command); + command.doCommand(); iter.remove(); } } diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchDao.java index a1bcd9319..2eed44c27 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchDao.java @@ -21,41 +21,263 @@ import java.util.Set; import li.strolch.model.StrolchRootElement; /** + *

+ * This data access object is used to access {@link StrolchRootElement} from the underlying persistence layer. Objects + * in Strolch are always referenced by a type and an ID. The type is a categorisation/grouping of the objects, while the + * ID is a unique identifier of the object. The ID must be unique, even for multiple groups. + *

+ * + *

+ * The DAO must support versioning. i.e. if versioning is enabled, then if an object is updated or removed, the existing + * version is not modified, but a new version is persisted so that rollbacks can be done + *

+ * * @author Robert von Burg + * + * @param + * the object instance being queried from the underlying persistence layer */ public interface StrolchDao { + /** + * Queries the set of IDs for all elements, regardless of types from the underlying persistence layer. + * + * @return the set of IDs for all elements + */ public Set queryKeySet(); + /** + * Returns true if an element exists with the given type and id in the underlying persistence layer + * + * @param type + * the type of elements + * @param id + * the id of the element to return + * + * @return true if an element exists with the given type and id + */ public boolean hasElement(String type, String id); + /** + * Returns the number of elements in the underlying persistence layer, regardless of type + * + * @return the number of elements in the underlying persistence layer + */ public long querySize(); + /** + * Returns the number of elements in the underlying persistence layer for the given type + * + * @return the number of elements in the underlying persistence layer for the given type + */ public long querySize(String type); + /** + * Queries the set of IDs for the elements with the given type + * + * @return the set of IDs for the elements with the given type + */ public Set queryKeySet(String type); + /** + * Queries the current list of types from the underlying persistence layer + * + * @return the list of types + */ public Set queryTypes(); + /** + * Queries the latest version of the element with the given type and ID + * + * @param type + * the type of the element to be queried + * @param id + * the id of the element to be queried + * + * @return the element with the given type and ID, or null if it does not exist + */ public T queryBy(String type, String id); + /** + *

+ * Queries a specific version of the element with the given type and ID. + *

+ * + *

+ * Note: If you want to query the latest version, then use the method with out the version parameter + *

+ * + * @param type + * the type of the element to be queried + * @param id + * the id of the element to be queried + * @param version + * the version of the element to be returned + * + * @return the element with the given type and ID, or null if it does not exist + */ + public T queryBy(String type, String id, int version); + + /** + * Queries and returns all the versions of the element with the given type and ID + * + * @param type + * the type of the element to be queried + * @param id + * the id of the element to be queried + * + * @return all the versions of the element with the given type and ID + */ + public List queryVersionsFor(String type, String id); + + /** + * Queries and returns all elements regardless of type + * + * @return all elements regardless of type + */ public List queryAll(); + /** + * Queries and returns all elements of the given type + * + * @param type + * the type of element to return + * + * @return all elements of the given type + */ public List queryAll(String type); - public void save(T element); + /** + * Persists the given element. The element must not yet exist + * + * @param element + * the element to be persisted + * + * @throws StrolchPersistenceException + * if the element already exists + */ + public void save(T element) throws StrolchPersistenceException; - public void saveAll(List elements); + /** + * Persists the given list of elements. None of the elements may already exists + * + * @param elements + * the list of elements to be persisted + * + * @throws StrolchPersistenceException + * if any of the elements already exist + */ + public void saveAll(List elements) throws StrolchPersistenceException; - public void update(T element); + /** + * Updates the given element. The element must already exist + * + * @param element + * the element to be updated + * + * @throws StrolchPersistenceException + * if the element does not exist + */ + public void update(T element) throws StrolchPersistenceException; - public void updateAll(List elements); + /** + * Updates the given list of elements. Each element must already exist + * + * @param elements + * the elements to be updated + * + * @throws StrolchPersistenceException + * if any of the elements do not exist + */ + public void updateAll(List elements) throws StrolchPersistenceException; - public void remove(T element); + /** + *

+ * Removes the given element from the underlying persistence layer + *

+ * + *

+ * Note: This method deletes the given object including its versions! Do not call this method if you want to + * use versioning! + *

+ * + * @param element + * the element to be removed + * + * @throws StrolchPersistenceException + * if the element does not exist + */ + public void remove(T element) throws StrolchPersistenceException; - public void removeAll(List elements); + /** + *

+ * Removes the given elements from the underlying persistence layer + *

+ * + *

+ * Note: This method deletes the given objects including their versions! Do not call this method if you want + * to use versioning! + *

+ * + * + * @param elements + * the elements to be removed + * + * @throws StrolchPersistenceException + * if any of the elements do not exist + */ + public void removeAll(List elements) throws StrolchPersistenceException; + /** + *

+ * Removes all elements regardless of type from the underlying persistence layer + *

+ * + *

+ * Note: This method does not support versioning. This method completely removes all objects regardless of + * type and their versions! + *

+ * + * @return the number of elements removed + * + * @throws StrolchPersistenceException + */ public long removeAll(); + /** + *

+ * Removes all elements of the given type from the underlying persistence layer + *

+ * + *

+ * Note: This method does not support versioning. This method completely removes all objects of the given + * type and their versions! + *

+ * + * @param type + * the type of element to remove + * + * @return the number of elements removed + */ public long removeAllBy(String type); + + /** + *

+ * Removes the given version of the given element from the underlying persistence layer. The version must be the + * latest version and thus always deletes the newest version. To delete multiple versions call this method multiple + * times. To remove it completely, call the {@link #remove(StrolchRootElement)} method. + *

+ * + *

+ * Note: This element given must be the current latest version! + *

+ * + * @param element + * the latest version of the element to be removed + * + * @throws StrolchPersistenceException + * if the element/version does not exist + */ + public void removeVersion(T element) throws StrolchPersistenceException; + } diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java index 87a085499..255faddb8 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java @@ -279,19 +279,6 @@ public interface StrolchTransaction extends AutoCloseable { */ public boolean isSuppressAudits(); - /** - * - * @param versioningEnabled - */ - public void setVersioningEnabled(boolean versioningEnabled); - - /** - * Returns true if versioning of objects is enabled - * - * @return true if versioning of objects is enabled - */ - public boolean isVersioningEnabled(); - /** * If the given argument is true, then logging of a {@link TransactionCloseStrategy#DO_NOTHING} will be suppressed * @@ -307,6 +294,13 @@ public interface StrolchTransaction extends AutoCloseable { */ boolean isSuppressDoNothingLogging(); + /** + * Returns true if versioning is enabled on the {@link StrolchRealm} for which this transaction has been opened + * + * @return true if versioning is enabled + */ + boolean isVersioningEnabled(); + /** * Locks the given element and registers it on the transaction so the lock is released when the transaction is * closed @@ -376,21 +370,6 @@ public interface StrolchTransaction extends AutoCloseable { */ public Audit auditFrom(AccessType accessType, StrolchRootElement element); - /** - * Creates a new version for the given element. If the element has no version yet, then the result will be version - * 0, otherwise the version will be an increment to the current version - * - * @param element - * the element for which to create a new version - * @param deleted - * if true, then the version will be marked as deleted, i.e. this object was removed from the element - * maps - * - * @return the new version, which is either an increment of the existing version on the element, or it will be - * version 0 - */ - public void updateVersionFor(StrolchRootElement element, boolean deleted); - /** *

* Performs the given {@link OrderQuery} and each returned {@link Order} is passed through the {@link OrderVisitor} diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryActivityDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryActivityDao.java index 24cc74856..501ba4f10 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryActivityDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryActivityDao.java @@ -25,6 +25,10 @@ import li.strolch.runtime.query.inmemory.InMemoryQuery; public class InMemoryActivityDao extends InMemoryDao implements ActivityDao { + public InMemoryActivityDao(boolean versioningEnabled) { + super(versioningEnabled); + } + @Override public List doQuery(ActivityQuery activityQuery) { InMemoryActivityQueryVisitor visitor = new InMemoryActivityQueryVisitor(); diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryDao.java index c220d619a..9ad89ce78 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryDao.java @@ -16,6 +16,7 @@ package li.strolch.persistence.inmemory; import java.text.MessageFormat; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -29,25 +30,32 @@ import li.strolch.persistence.api.StrolchPersistenceException; public class InMemoryDao implements StrolchDao { - private Map> elementMap; + private Map>> elementMap; + private boolean versioningEnabled; - public InMemoryDao() { + public InMemoryDao(boolean versioningEnabled) { + this.versioningEnabled = versioningEnabled; this.elementMap = new HashMap<>(); } @Override public synchronized boolean hasElement(String type, String id) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); if (byType == null) return false; - return byType.containsKey(id); + + ArrayDeque list = byType.get(id); + if (list == null) + return false; + + return !list.getLast().getVersion().isDeleted(); } @Override public synchronized long querySize() { long size = 0; for (String type : this.elementMap.keySet()) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); size += byType.size(); } return size; @@ -55,7 +63,7 @@ public class InMemoryDao implements StrolchDao @Override public synchronized long querySize(String type) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); if (byType == null) return 0; return byType.size(); @@ -63,21 +71,19 @@ public class InMemoryDao implements StrolchDao @Override public synchronized Set queryKeySet() { - Set keySet = new HashSet<>(); for (String type : this.elementMap.keySet()) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); for (String id : byType.keySet()) { keySet.add(id); } } - return keySet; } @Override public synchronized Set queryKeySet(String type) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); if (byType == null) return new HashSet<>(0); return new HashSet<>(byType.keySet()); @@ -90,19 +96,58 @@ public class InMemoryDao implements StrolchDao @Override public synchronized T queryBy(String type, String id) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); if (byType == null) return null; - return byType.get(id); + ArrayDeque list = byType.get(id); + if (list == null) + return null; + + T t = list.getLast(); + if (t.getVersion() != null && t.getVersion().isDeleted()) + return null; + + return t; + } + + @Override + public synchronized T queryBy(String type, String id, int version) { + Map> byType = this.elementMap.get(type); + if (byType == null) + return null; + ArrayDeque list = byType.get(id); + if (list == null) + return null; + + for (T t : list) { + if (t.getVersion().getVersion() == version) + return t; + } + + return null; + } + + @Override + public synchronized List queryVersionsFor(String type, String id) { + Map> byType = this.elementMap.get(type); + if (byType == null) + return null; + ArrayDeque list = byType.get(id); + if (list == null) + return new ArrayList<>(); + return new ArrayList<>(list); } @Override public synchronized List queryAll() { List elements = new ArrayList<>(); for (String type : this.elementMap.keySet()) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); for (String id : byType.keySet()) { - elements.add(byType.get(id)); + ArrayDeque list = byType.get(id); + T last = list.getLast(); + if (last.getVersion() == null || !last.getVersion().isDeleted()) + elements.add(last); } } @@ -111,27 +156,43 @@ public class InMemoryDao implements StrolchDao @Override public synchronized List queryAll(String type) { - Map byType = this.elementMap.get(type); + Map> byType = this.elementMap.get(type); if (byType == null) return new ArrayList<>(0); - return new ArrayList<>(byType.values()); + + List elements = new ArrayList<>(); + for (ArrayDeque list : byType.values()) { + T last = list.getLast(); + if (last.getVersion() == null || !last.getVersion().isDeleted()) + elements.add(last); + } + + return elements; } @Override public synchronized void save(T element) { - Map byType = this.elementMap.get(element.getType()); + Map> byType = this.elementMap.get(element.getType()); if (byType == null) { byType = new HashMap<>(); this.elementMap.put(element.getType(), byType); } - if (byType.containsKey(element.getId())) { + ArrayDeque list = byType.get(element.getId()); + + // only allow add for existing id, if the existing one is "deleted" + if (list != null && !list.getLast().getVersion().isDeleted()) { String msg = "An element already exists with the id {0}. Elements of the same class must always have a unique id, regardless of their type!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, element.getId()); throw new StrolchPersistenceException(msg); } - byType.put(element.getId(), element); + if (list == null) { + list = new ArrayDeque<>(); + byType.put(element.getId(), list); + } + + list.addLast(element); } @Override @@ -143,13 +204,24 @@ public class InMemoryDao implements StrolchDao @Override public synchronized void update(T element) { - Map byType = this.elementMap.get(element.getType()); + Map> byType = this.elementMap.get(element.getType()); if (byType == null) { - byType = new HashMap<>(); - this.elementMap.put(element.getType(), byType); + String msg = "The element does not yet exist with the type {0} and id {1}. Use add() for new objects!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, element.getType(), element.getId()); + throw new StrolchPersistenceException(msg); } - byType.put(element.getId(), element); + ArrayDeque list = byType.get(element.getId()); + if (list == null) { + String msg = "The element does not yet exist with the type {0} and id {1}. Use add() for new objects!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, element.getType(), element.getId()); + throw new StrolchPersistenceException(msg); + } + + if (!this.versioningEnabled) + list.clear(); + + list.addLast(element); } @Override @@ -161,7 +233,7 @@ public class InMemoryDao implements StrolchDao @Override public synchronized void remove(T element) { - Map byType = this.elementMap.get(element.getType()); + Map> byType = this.elementMap.get(element.getType()); if (byType != null) { byType.remove(element.getId()); @@ -184,7 +256,7 @@ public class InMemoryDao implements StrolchDao Set keySet = new HashSet<>(this.elementMap.keySet()); for (String type : keySet) { - Map byType = this.elementMap.remove(type); + Map> byType = this.elementMap.remove(type); removed += byType.size(); byType.clear(); } @@ -194,11 +266,42 @@ public class InMemoryDao implements StrolchDao @Override public synchronized long removeAllBy(String type) { - Map byType = this.elementMap.remove(type); + Map> byType = this.elementMap.remove(type); if (byType == null) return 0; long removed = byType.size(); byType.clear(); return removed; } + + @Override + public synchronized void removeVersion(T element) throws StrolchPersistenceException { + + Map> byType = this.elementMap.get(element.getType()); + if (byType == null) { + String msg = "The element does not yet exist with the type {0}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, element.getType()); + throw new StrolchPersistenceException(msg); + } + + ArrayDeque list = byType.get(element.getId()); + if (list == null) { + String msg = "The element does not yet exist with the type {0} and id {1}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, element.getType(), element.getId()); + throw new StrolchPersistenceException(msg); + } + + T last = list.getLast(); + if (!last.getVersion().equals(element.getVersion())) { + String msg = "The current version {0} is not the same as the version to remove {1}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, last.getVersion(), element.getVersion()); + throw new StrolchPersistenceException(msg); + } + + list.removeLast(); + + if (list.isEmpty()) { + byType.remove(element.getId()); + } + } } diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryOrderDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryOrderDao.java index af629543c..0c9990db2 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryOrderDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryOrderDao.java @@ -25,6 +25,10 @@ import li.strolch.runtime.query.inmemory.InMemoryQuery; public class InMemoryOrderDao extends InMemoryDao implements OrderDao { + public InMemoryOrderDao(boolean versioningEnabled) { + super(versioningEnabled); + } + @Override public List doQuery(OrderQuery orderQuery) { InMemoryOrderQueryVisitor visitor = new InMemoryOrderQueryVisitor(); diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java index d5a5fe089..be5aec095 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java @@ -30,11 +30,13 @@ import li.strolch.runtime.privilege.PrivilegeHandler; public class InMemoryPersistence implements PersistenceHandler { + private boolean versioningEnabled; private Map daoCache; private PrivilegeHandler privilegeHandler; - public InMemoryPersistence(PrivilegeHandler privilegeHandler) { + public InMemoryPersistence(PrivilegeHandler privilegeHandler, boolean versioningEnabled) { this.privilegeHandler = privilegeHandler; + this.versioningEnabled = versioningEnabled; this.daoCache = new HashMap<>(); } @@ -75,7 +77,8 @@ public class InMemoryPersistence implements PersistenceHandler { private synchronized DaoCache getDaoCache(StrolchTransaction tx) { DaoCache daoCache = this.daoCache.get(tx.getRealmName()); if (daoCache == null) { - daoCache = new DaoCache(new InMemoryOrderDao(), new InMemoryResourceDao(), new InMemoryActivityDao(), + daoCache = new DaoCache(new InMemoryOrderDao(this.versioningEnabled), + new InMemoryResourceDao(this.versioningEnabled), new InMemoryActivityDao(this.versioningEnabled), new InMemoryAuditDao()); this.daoCache.put(tx.getRealmName(), daoCache); } diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryResourceDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryResourceDao.java index 3dd198682..966694cbc 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryResourceDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryResourceDao.java @@ -25,6 +25,10 @@ import li.strolch.runtime.query.inmemory.InMemoryResourceQueryVisitor; public class InMemoryResourceDao extends InMemoryDao implements ResourceDao { + public InMemoryResourceDao(boolean versioningEnabled) { + super(versioningEnabled); + } + @Override public List doQuery(ResourceQuery resourceQuery) { InMemoryResourceQueryVisitor visitor = new InMemoryResourceQueryVisitor(); diff --git a/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java b/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java index 810838da8..f799c76aa 100644 --- a/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java @@ -50,6 +50,7 @@ public class ComponentContainerTest { public static final String PATH_CACHED_CONTAINER = "src/test/resources/cachedtest"; public static final String PATH_EMPTY_CONTAINER = "src/test/resources/emptytest"; public static final String PATH_MINIMAL_CONTAINER = "src/test/resources/minimaltest"; + public static final String PATH_VERSIONING_CONTAINER = "src/test/resources/versioningtest"; public static final String PATH_REALM_RUNTIME = "target/realmtest/"; public static final String PATH_TRANSIENT_RUNTIME = "target/transienttest/"; diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryTest.java new file mode 100644 index 000000000..ba0bda78e --- /dev/null +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryTest.java @@ -0,0 +1,319 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.runtime.query.inmemory; + +import static li.strolch.model.query.ParameterSelection.booleanSelection; +import static li.strolch.model.query.ParameterSelection.floatListSelection; +import static li.strolch.model.query.ParameterSelection.floatSelection; +import static li.strolch.model.query.ParameterSelection.integerListSelection; +import static li.strolch.model.query.ParameterSelection.longListSelection; +import static li.strolch.model.query.ParameterSelection.stringListSelection; +import static li.strolch.model.query.ParameterSelection.stringSelection; +import static li.strolch.utils.StringMatchMode.ci; +import static li.strolch.utils.StringMatchMode.es; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import li.strolch.model.ModelGenerator; +import li.strolch.model.Order; +import li.strolch.model.ParameterBag; +import li.strolch.model.State; +import li.strolch.model.Version; +import li.strolch.model.parameter.BooleanParameter; +import li.strolch.model.parameter.FloatListParameter; +import li.strolch.model.parameter.FloatParameter; +import li.strolch.model.parameter.IntegerListParameter; +import li.strolch.model.parameter.LongListParameter; +import li.strolch.model.parameter.StringListParameter; +import li.strolch.model.parameter.StringParameter; +import li.strolch.model.query.IdSelection; +import li.strolch.model.query.NameSelection; +import li.strolch.model.query.OrderQuery; +import li.strolch.model.query.ParameterSelection; +import li.strolch.persistence.inmemory.InMemoryOrderDao; + +/** + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class InMemoryOrderQueryTest { + + protected InMemoryOrderDao daoInstance() { + return new InMemoryOrderDao(false); + } + + @Test + public void shouldQueryById() { + + List orders = getOrders(); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery orderQuery = OrderQuery.query("MyType1"); + orderQuery.with(new IdSelection("@1")); + + List result = dao.doQuery(orderQuery); + assertEquals(1, result.size()); + assertEquals("@1", result.get(0).getId()); + } + + @Test + public void shouldQueryByIdOr() { + + List orders = getOrders(); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery orderQuery = OrderQuery.query("MyType2"); + orderQuery.or().with(new IdSelection("@3"), new IdSelection("@4")); + + List result = dao.doQuery(orderQuery); + assertEquals(2, result.size()); + assertEquals("@3", result.get(0).getId()); + assertEquals("@4", result.get(1).getId()); + } + + @Test + public void shouldQueryByIdAnd() { + + List orders = getOrders(); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery orderQuery = OrderQuery.query("MyType2"); + orderQuery.and().with(new IdSelection("@3"), new NameSelection("Res 3", es())); + + List result = dao.doQuery(orderQuery); + assertEquals(1, result.size()); + assertEquals("@3", result.get(0).getId()); + } + + @Test + public void shouldNotQueryByIdAnd() { + + List orders = getOrders(); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery orderQuery = OrderQuery.query("MyType1"); + orderQuery.and().with(new IdSelection("@3"), new NameSelection("@4", es())); + + List result = dao.doQuery(orderQuery); + assertEquals(0, result.size()); + } + + @Test + public void shouldQueryByParameter() { + + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with( + // + stringSelection("parameters", "color", "red", es()), + booleanSelection("parameters", "forChildren", true), floatSelection("parameters", "diameter", 22.0)); + + List result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + @Test + public void shouldQueryByListParameter() { + + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery; + List result; + + // string list + { + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(stringListSelection("parameters", "stringListValues", Arrays.asList("a", "z"))); + result = dao.doQuery(ballQuery); + assertEquals(0, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(stringListSelection("parameters", "stringListValues", Arrays.asList("a"))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(stringListSelection("parameters", "stringListValues", Arrays.asList("c", "b", "a"))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + // integer list + { + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(integerListSelection("parameters", "intListValues", Arrays.asList(1, 5))); + result = dao.doQuery(ballQuery); + assertEquals(0, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(integerListSelection("parameters", "intListValues", Arrays.asList(1))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(integerListSelection("parameters", "intListValues", Arrays.asList(3, 2, 1))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + // float list + { + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(floatListSelection("parameters", "floatListValues", Arrays.asList(4.0, 8.0))); + result = dao.doQuery(ballQuery); + assertEquals(0, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(floatListSelection("parameters", "floatListValues", Arrays.asList(4.0))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(floatListSelection("parameters", "floatListValues", Arrays.asList(6.2, 5.1, 4.0))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + // long list + { + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(longListSelection("parameters", "longListValues", Arrays.asList(8L, 11L))); + result = dao.doQuery(ballQuery); + assertEquals(0, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(longListSelection("parameters", "longListValues", Arrays.asList(8L))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + + ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with(longListSelection("parameters", "longListValues", Arrays.asList(10L, 9L, 8L))); + result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + } + + @Test + public void shouldQueryByNullParameter1() { + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with( // + ParameterSelection.nullSelection("parameters", "color")); + + List result = dao.doQuery(ballQuery); + assertEquals(0, result.size()); + } + + @Test + public void shouldQueryByNullParameter2() { + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with( // + ParameterSelection.nullSelection("parameters", "weight")); + + List result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + @Test + public void shouldQueryByNullParameter3() { + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery = OrderQuery.query("Ball"); + ballQuery.and().with( // + ParameterSelection.nullSelection("parameters", "weight")); + + List result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + @Test + public void shouldQueryByName() { + + List orders = getOrders(); + orders.add(getBallOrder()); + InMemoryOrderDao dao = daoInstance(); + dao.saveAll(orders); + + OrderQuery ballQuery = OrderQuery.query("Ball"); + ballQuery.with(new NameSelection("ball ", ci())); + + List result = dao.doQuery(ballQuery); + assertEquals(1, result.size()); + } + + private Order getBallOrder() { + Order o1 = new Order("childrensBall", "Ball 1", "Ball"); + o1.setVersion(new Version(o1.getLocator(), 0, "ModelGenerator", false)); + ParameterBag bag = new ParameterBag("parameters", "Ball Details", "Parameters"); + bag.addParameter(new StringParameter("color", "Color", "red")); + bag.addParameter(new BooleanParameter("forChildren", "Color", true)); + bag.addParameter(new FloatParameter("diameter", "Color", 22.0)); + bag.addParameter( + new StringListParameter("stringListValues", "List of String Values", Arrays.asList("a", "b", "c"))); + bag.addParameter(new IntegerListParameter("intListValues", "List of Integer Values", Arrays.asList(1, 2, 3))); + bag.addParameter( + new FloatListParameter("floatListValues", "List of Float Values", Arrays.asList(4.0, 5.1, 6.2))); + bag.addParameter(new LongListParameter("longListValues", "List of Long Values", Arrays.asList(8L, 9L, 10L))); + o1.addParameterBag(bag); + return o1; + } + + private List getOrders() { + Order res1 = ModelGenerator.createOrder("@1", "Res 1", "MyType1", new Date(), State.CREATED); + Order res2 = ModelGenerator.createOrder("@2", "Res 2", "MyType1", new Date(), State.CREATED); + Order res3 = ModelGenerator.createOrder("@3", "Res 3", "MyType2", new Date(), State.CREATED); + Order res4 = ModelGenerator.createOrder("@4", "Res 4", "MyType2", new Date(), State.CREATED); + Order res5 = ModelGenerator.createOrder("@5", "Res 5", "MyType3", new Date(), State.CREATED); + Order res6 = ModelGenerator.createOrder("@6", "Res 6", "MyType3", new Date(), State.CREATED); + List orders = new ArrayList<>(); + orders.add(res1); + orders.add(res2); + orders.add(res3); + orders.add(res4); + orders.add(res5); + orders.add(res6); + return orders; + } +} diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryPersistenceHandler.java similarity index 94% rename from li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java rename to li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryPersistenceHandler.java index 2d09ce609..e6adf2420 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryPersistenceHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.persistence.inmemory; +package li.strolch.runtime.query.inmemory; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; @@ -24,6 +24,7 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.persistence.inmemory.InMemoryPersistence; import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; @@ -44,7 +45,7 @@ public class InMemoryPersistenceHandler extends StrolchComponent implements Pers @Override public void initialize(ComponentConfiguration configuration) throws Exception { - this.persistence = new InMemoryPersistence(getContainer().getPrivilegeHandler()); + this.persistence = new InMemoryPersistence(getContainer().getPrivilegeHandler(), false); super.initialize(configuration); } diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryTest.java similarity index 75% rename from li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java rename to li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryTest.java index 3dbca950a..c38c08b4a 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryTest.java @@ -1,18 +1,3 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package li.strolch.runtime.query.inmemory; import static li.strolch.model.query.ParameterSelection.booleanSelection; @@ -28,14 +13,14 @@ import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; +import org.junit.Test; + import li.strolch.model.ModelGenerator; -import li.strolch.model.Order; import li.strolch.model.ParameterBag; import li.strolch.model.Resource; -import li.strolch.model.State; +import li.strolch.model.Version; import li.strolch.model.parameter.BooleanParameter; import li.strolch.model.parameter.FloatListParameter; import li.strolch.model.parameter.FloatParameter; @@ -45,39 +30,21 @@ import li.strolch.model.parameter.StringListParameter; import li.strolch.model.parameter.StringParameter; import li.strolch.model.query.IdSelection; import li.strolch.model.query.NameSelection; -import li.strolch.model.query.OrderQuery; import li.strolch.model.query.ParameterSelection; import li.strolch.model.query.ResourceQuery; -import li.strolch.persistence.inmemory.InMemoryOrderDao; import li.strolch.persistence.inmemory.InMemoryResourceDao; -import org.junit.Test; +public class InMemoryResourceQueryTest { -/** - * @author Robert von Burg - */ -@SuppressWarnings("nls") -public class InMemoryQueryTest { - - @Test - public void shouldQueryOrderById() { - - List orders = getOrders(); - InMemoryOrderDao dao = new InMemoryOrderDao(); - dao.saveAll(orders); - - OrderQuery orderQuery = OrderQuery.query("MyType1"); - orderQuery.with(new IdSelection("@1")); - List result = dao.doQuery(orderQuery); - assertEquals(1, result.size()); - assertEquals("@1", result.get(0).getId()); + protected InMemoryResourceDao daoInstance() { + return new InMemoryResourceDao(false); } @Test - public void shouldQueryResourceById() { + public void shouldQueryById() { List resources = getResources(); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery resourceQuery = ResourceQuery.query("MyType1"); @@ -89,10 +56,10 @@ public class InMemoryQueryTest { } @Test - public void shouldQueryResourceByIdOr() { + public void shouldQueryByIdOr() { List resources = getResources(); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery resourceQuery = ResourceQuery.query("MyType2"); @@ -105,10 +72,10 @@ public class InMemoryQueryTest { } @Test - public void shouldQueryResourceByIdAnd() { + public void shouldQueryByIdAnd() { List resources = getResources(); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery resourceQuery = ResourceQuery.query("MyType2"); @@ -120,10 +87,10 @@ public class InMemoryQueryTest { } @Test - public void shouldNotQueryResourceByIdAnd() { + public void shouldNotQueryByIdAnd() { List resources = getResources(); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery resourceQuery = ResourceQuery.query("MyType1"); @@ -138,7 +105,7 @@ public class InMemoryQueryTest { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery = ResourceQuery.query("Ball"); @@ -156,7 +123,7 @@ public class InMemoryQueryTest { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery; @@ -239,7 +206,7 @@ public class InMemoryQueryTest { public void shouldQueryByNullParameter1() { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery = ResourceQuery.query("Ball"); @@ -254,7 +221,7 @@ public class InMemoryQueryTest { public void shouldQueryByNullParameter2() { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery = ResourceQuery.query("Ball"); @@ -269,7 +236,7 @@ public class InMemoryQueryTest { public void shouldQueryByNullParameter3() { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery = ResourceQuery.query("Ball"); @@ -285,7 +252,7 @@ public class InMemoryQueryTest { List resources = getResources(); resources.add(getBallResource()); - InMemoryResourceDao dao = new InMemoryResourceDao(); + InMemoryResourceDao dao = daoInstance(); dao.saveAll(resources); ResourceQuery ballQuery = ResourceQuery.query("Ball"); @@ -297,14 +264,16 @@ public class InMemoryQueryTest { private Resource getBallResource() { Resource res1 = new Resource("childrensBall", "Ball 1", "Ball"); + res1.setVersion(new Version(res1.getLocator(), 0, "ModelGenerator", false)); ParameterBag bag = new ParameterBag("parameters", "Ball Details", "Parameters"); bag.addParameter(new StringParameter("color", "Color", "red")); bag.addParameter(new BooleanParameter("forChildren", "Color", true)); bag.addParameter(new FloatParameter("diameter", "Color", 22.0)); - bag.addParameter(new StringListParameter("stringListValues", "List of String Values", Arrays.asList("a", "b", - "c"))); + bag.addParameter( + new StringListParameter("stringListValues", "List of String Values", Arrays.asList("a", "b", "c"))); bag.addParameter(new IntegerListParameter("intListValues", "List of Integer Values", Arrays.asList(1, 2, 3))); - bag.addParameter(new FloatListParameter("floatListValues", "List of Float Values", Arrays.asList(4.0, 5.1, 6.2))); + bag.addParameter( + new FloatListParameter("floatListValues", "List of Float Values", Arrays.asList(4.0, 5.1, 6.2))); bag.addParameter(new LongListParameter("longListValues", "List of Long Values", Arrays.asList(8L, 9L, 10L))); res1.addParameterBag(bag); return res1; @@ -324,23 +293,11 @@ public class InMemoryQueryTest { resources.add(res4); resources.add(res5); resources.add(res6); + + for (Resource resource : resources) { + resource.setVersion(new Version(resource.getLocator(), 0, "Test", false)); + } + return resources; } - - private List getOrders() { - Order res1 = ModelGenerator.createOrder("@1", "Res 1", "MyType1", new Date(), State.CREATED); - Order res2 = ModelGenerator.createOrder("@2", "Res 2", "MyType1", new Date(), State.CREATED); - Order res3 = ModelGenerator.createOrder("@3", "Res 3", "MyType2", new Date(), State.CREATED); - Order res4 = ModelGenerator.createOrder("@4", "Res 4", "MyType2", new Date(), State.CREATED); - Order res5 = ModelGenerator.createOrder("@5", "Res 5", "MyType3", new Date(), State.CREATED); - Order res6 = ModelGenerator.createOrder("@6", "Res 6", "MyType3", new Date(), State.CREATED); - List orders = new ArrayList<>(); - orders.add(res1); - orders.add(res2); - orders.add(res3); - orders.add(res4); - orders.add(res5); - orders.add(res6); - return orders; - } } diff --git a/li.strolch.agent/src/test/resources/cachedtest/config/StrolchConfiguration.xml b/li.strolch.agent/src/test/resources/cachedtest/config/StrolchConfiguration.xml index 485927bfb..0592be934 100644 --- a/li.strolch.agent/src/test/resources/cachedtest/config/StrolchConfiguration.xml +++ b/li.strolch.agent/src/test/resources/cachedtest/config/StrolchConfiguration.xml @@ -44,7 +44,7 @@ PersistenceHandler li.strolch.persistence.api.PersistenceHandler - li.strolch.persistence.inmemory.InMemoryPersistenceHandler + li.strolch.runtime.query.inmemory.InMemoryPersistenceHandler \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/realmtest/config/StrolchConfiguration.xml b/li.strolch.agent/src/test/resources/realmtest/config/StrolchConfiguration.xml index 7d8bd7c36..fd116ae0a 100644 --- a/li.strolch.agent/src/test/resources/realmtest/config/StrolchConfiguration.xml +++ b/li.strolch.agent/src/test/resources/realmtest/config/StrolchConfiguration.xml @@ -19,7 +19,7 @@ PersistenceHandler li.strolch.persistence.api.PersistenceHandler - li.strolch.persistence.inmemory.InMemoryPersistenceHandler + li.strolch.runtime.query.inmemory.InMemoryPersistenceHandler RealmHandler diff --git a/li.strolch.agent/src/test/resources/transactionaltest/config/StrolchConfiguration.xml b/li.strolch.agent/src/test/resources/transactionaltest/config/StrolchConfiguration.xml index 8c71a31fc..38e1d8566 100644 --- a/li.strolch.agent/src/test/resources/transactionaltest/config/StrolchConfiguration.xml +++ b/li.strolch.agent/src/test/resources/transactionaltest/config/StrolchConfiguration.xml @@ -45,7 +45,7 @@ PersistenceHandler li.strolch.persistence.api.PersistenceHandler - li.strolch.persistence.inmemory.InMemoryPersistenceHandler + li.strolch.runtime.query.inmemory.InMemoryPersistenceHandler \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml new file mode 100644 index 000000000..cfab85b24 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeRoles.xml new file mode 100644 index 000000000..3a9e8ab07 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeRoles.xml @@ -0,0 +1,16 @@ + + + + + li.strolch.agent.impl.StartRealms + + + + + true + + + true + + + diff --git a/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeUsers.xml b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeUsers.xml new file mode 100644 index 000000000..2973bf8f5 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/config/PrivilegeUsers.xml @@ -0,0 +1,18 @@ + + + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en_GB + + AppUser + + + diff --git a/li.strolch.agent/src/test/resources/versioningtest/config/StrolchConfiguration.xml b/li.strolch.agent/src/test/resources/versioningtest/config/StrolchConfiguration.xml new file mode 100644 index 000000000..392d5cdd8 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/config/StrolchConfiguration.xml @@ -0,0 +1,46 @@ + + + + + StrolchRuntimeTest + + true + + + + PrivilegeHandler + li.strolch.runtime.privilege.PrivilegeHandler + li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler + + PrivilegeConfig.xml + + + + RealmHandler + li.strolch.agent.api.RealmHandler + li.strolch.agent.impl.DefaultRealmHandler + PrivilegeHandler + + true + TRANSIENT + StrolchModel.xml + + + + ServiceHandler + li.strolch.runtime.configuration.model.ServiceHandlerTest + li.strolch.runtime.configuration.model.ServiceHandlerTestImpl + RealmHandler + + + + + PostInitializer + li.strolch.runtime.configuration.model.PostInitializerTest + li.strolch.runtime.configuration.model.PostInitializerTestImpl + ServiceHandler + + + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/versioningtest/data/Orders.xml b/li.strolch.agent/src/test/resources/versioningtest/data/Orders.xml new file mode 100644 index 000000000..55358bcaa --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/data/Orders.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/li.strolch.agent/src/test/resources/versioningtest/data/Resources.xml b/li.strolch.agent/src/test/resources/versioningtest/data/Resources.xml new file mode 100644 index 000000000..e6259cb83 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/data/Resources.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/li.strolch.agent/src/test/resources/versioningtest/data/StrolchModel.xml b/li.strolch.agent/src/test/resources/versioningtest/data/StrolchModel.xml new file mode 100644 index 000000000..1e2f02da5 --- /dev/null +++ b/li.strolch.agent/src/test/resources/versioningtest/data/StrolchModel.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.model/src/main/java/li/strolch/model/Locator.java b/li.strolch.model/src/main/java/li/strolch/model/Locator.java index 89d0dada4..69ce6aede 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Locator.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Locator.java @@ -22,6 +22,7 @@ import java.util.Iterator; import java.util.List; import li.strolch.exception.StrolchException; +import li.strolch.utils.dbc.DBC; import li.strolch.utils.helper.StringHelper; /** @@ -126,6 +127,11 @@ public class Locator { this.pathElements = Collections.unmodifiableList(fullPath); } + public String get(int part) { + DBC.PRE.assertTrue("Part outside of locator range: " + part, part < this.pathElements.size()); + return this.pathElements.get(part); + } + /** * Returns the immutable list of path elements making up this locator * @@ -155,7 +161,7 @@ public class Locator { public Locator append(List subPathElements) { return new Locator(this.pathElements, subPathElements); } - + /** * Returns a new {@link Locator} where the given sub path is appended to the locator * diff --git a/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java b/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java index 958c0bac0..bfab420cb 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java +++ b/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java @@ -346,6 +346,33 @@ public class ModelGenerator { return orders; } + /** + * Creates a list of {@link Activity Activities} with the given values and adds a {@link ParameterBag} by calling + * {@link #createParameterBag(String, String, String)} + * + * @param idStart + * id range start + * @param count + * the number of elements to create + * @param idPrefix + * the prefix to generate IDs for the {@link Activity Activities} + * @param name + * the name of the {@link Activity} + * @param type + * the type of the {@link Activity} + * + * @return the list of newly created {@link Activity Activities} + */ + + public static List createActivities(int idStart, int count, String idPrefix, String name, String type) { + List activities = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String id = StringHelper.normalizeLength(String.valueOf((i + idStart)), 8, true, '0'); + activities.add(createActivity(idPrefix + id, name + " " + i, type)); + } + return activities; + } + public static Activity createActivity(String id, String name, String type) { Activity rootActivity = new Activity(id, name, type); diff --git a/li.strolch.model/src/main/java/li/strolch/model/Order.java b/li.strolch.model/src/main/java/li/strolch/model/Order.java index e20b00d4f..6dec3280b 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Order.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Order.java @@ -15,6 +15,7 @@ */ package li.strolch.model; +import java.text.MessageFormat; import java.util.Date; import li.strolch.exception.StrolchPolicyException; @@ -86,8 +87,11 @@ public class Order extends GroupedParameterizedElement implements StrolchRootEle @Override public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException { - if (this.version != null) - this.version.validateIsNext(version); + if (version != null && !getLocator().equals(version.getLocator())) { + String msg = "Illegal version as locator is not same: Element: {0} Version: {1}"; + throw new IllegalArgumentException(MessageFormat.format(msg, getLocator(), version)); + } + this.version = version; } diff --git a/li.strolch.model/src/main/java/li/strolch/model/Resource.java b/li.strolch.model/src/main/java/li/strolch/model/Resource.java index b682856e8..56557c846 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Resource.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Resource.java @@ -15,6 +15,7 @@ */ package li.strolch.model; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -66,8 +67,11 @@ public class Resource extends GroupedParameterizedElement implements StrolchRoot @Override public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException { - if (this.version != null) - this.version.validateIsNext(version); + if (version != null && !getLocator().equals(version.getLocator())) { + String msg = "Illegal version as locator is not same: Element: {0} Version: {1}"; + throw new IllegalArgumentException(MessageFormat.format(msg, getLocator(), version)); + } + this.version = version; } @@ -140,6 +144,7 @@ public class Resource extends GroupedParameterizedElement implements StrolchRoot @Override public Resource getClone() { Resource clone = new Resource(); + super.fillClone(clone); if (this.timedStateMap != null) { diff --git a/li.strolch.model/src/main/java/li/strolch/model/StrolchRootElement.java b/li.strolch.model/src/main/java/li/strolch/model/StrolchRootElement.java index 890940791..3528c16c2 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/StrolchRootElement.java +++ b/li.strolch.model/src/main/java/li/strolch/model/StrolchRootElement.java @@ -34,23 +34,16 @@ public interface StrolchRootElement extends StrolchElement, PolicyContainer, Par /** *

- * Sets the version of this object. - *

- * - *

- * Note: If the version is set, then the new version must have the {@link Version#getVersion()} be an - * increment to the current version! + * Sets the version of this object *

* * @param version * the version to set * * @throws IllegalArgumentException - * if the given version's locator is not equal to the current version's locator - * @throws IllegalStateException - * if the given version is not the next version (an increment) + * if the given version's locator is not equal to the current element's locator */ - public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException; + public void setVersion(Version version) throws IllegalArgumentException; /** * Visitor pattern accept method. Takes a {@link StrolchRootElementVisitor} to visit this element diff --git a/li.strolch.model/src/main/java/li/strolch/model/Version.java b/li.strolch.model/src/main/java/li/strolch/model/Version.java index 5c3eca43f..e139ce93a 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Version.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Version.java @@ -20,6 +20,10 @@ import li.strolch.utils.dbc.DBC; * A version has a flag delete which, if true, designates that this version was removed *

* + *

+ * A {@link Version} is immutable + *

+ * * @author Robert von Burg */ public class Version { @@ -34,12 +38,12 @@ public class Version { * Creates a new version instance with the given values. The creation date is now. * * @param version - * the integer version which must be > 0 and should be incremented for each new version of an object + * the integer version which must be >= 0 and should be incremented for each new version of an object * @param createdBy * the username of the creator of this object */ public Version(Locator locator, int version, String createdBy, boolean deleted) { - DBC.PRE.assertTrue("Version must by > 0", version > 0); + DBC.PRE.assertTrue("Version must by >= 0", version >= 0); DBC.PRE.assertNotNull("locator must be set!", locator); DBC.PRE.assertNotNull("createdBy must be set!", createdBy); this.locator = locator; @@ -54,7 +58,7 @@ public class Version { } /** - * Returns the integer version, which is > 0 + * Returns the integer version, which is >= 0 * * @return the version */ @@ -123,7 +127,7 @@ public class Version { } /** - * Validates that the given argument is an increment to this version + * Validates that the given argument is a newer version to this version * * @param other * the other version to check @@ -133,14 +137,14 @@ public class Version { * @throws IllegalStateException * if the given argument is not the next version */ - public void validateIsNext(Version other) throws IllegalArgumentException, IllegalStateException { + public void validateIsNewer(Version other) throws IllegalArgumentException, IllegalStateException { if (!this.locator.equals(other.locator)) { String msg = "Other version {0} is not for same object: {1}"; throw new IllegalArgumentException(MessageFormat.format(msg, other, this.version)); } - if (other.version != this.version + 1) { - String msg = "Other version: {0} is not an increment to this version: {1}"; + if (this.version >= other.version) { + String msg = "Other version: {0} is a newer version of this version: {1}"; throw new IllegalArgumentException(MessageFormat.format(msg, other, this.version)); } } @@ -161,4 +165,80 @@ public class Version { builder.append("]"); return builder.toString(); } + + /** + * Returns the next version, i.e. this version incremented by 1 + * + * @param username + * the username to set + * @param deleted + * the deleted flag to set + * + * @return the next version + */ + public Version next(String username, boolean deleted) { + return new Version(this.locator, getNextVersion(), username, deleted); + } + + /** + * Sets the initial version = 0 for the given element which is also set to not deleted + * + * @param element + * the element for which to create a new version + * @param username + * the username of the user who created this version of the object + */ + public static void setInitialVersionFor(StrolchRootElement element, String username) { + Version version = new Version(element.getLocator(), 0, username, false); + element.setVersion(version); + } + + /** + * Sets a new version on the given element. If the element has no version yet, then the result will be version 0, + * otherwise the version will be an increment to the current version + * + * @param element + * the element for which to create a new version + * @param username + * the username of the user who created this version of the object + * @param deleted + * if true, then the version will be marked as deleted, i.e. this object was removed from the element + * maps + */ + public static void updateVersionFor(StrolchRootElement element, String username, boolean deleted) { + int v = element.getVersion() == null ? 0 : element.getVersion().getVersion() + 1; + Version version = new Version(element.getLocator(), v, username, deleted); + element.setVersion(version); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (this.deleted ? 1231 : 1237); + result = prime * result + ((this.locator == null) ? 0 : this.locator.hashCode()); + result = prime * result + this.version; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Version other = (Version) obj; + if (this.deleted != other.deleted) + return false; + if (this.locator == null) { + if (other.locator != null) + return false; + } else if (!this.locator.equals(other.locator)) + return false; + if (this.version != other.version) + return false; + return true; + } } diff --git a/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java b/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java index dd512c0ad..6e3bb29fc 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java +++ b/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java @@ -15,6 +15,7 @@ */ package li.strolch.model.activity; +import java.text.MessageFormat; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -78,8 +79,12 @@ public class Activity extends GroupedParameterizedElement public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException { if (!this.isRootElement()) throw new IllegalStateException("Can't set the version on non root of " + getLocator()); - if (this.version != null) - this.version.validateIsNext(version); + + if (version != null && !getLocator().equals(version.getLocator())) { + String msg = "Illegal version as locator is not same: Element: {0} Version: {1}"; + throw new IllegalArgumentException(MessageFormat.format(msg, getLocator(), version)); + } + this.version = version; } @@ -260,6 +265,7 @@ public class Activity extends GroupedParameterizedElement @Override public Activity getClone() { Activity clone = new Activity(); + super.fillClone(clone); if (this.elements == null) diff --git a/li.strolch.model/src/main/java/li/strolch/model/visitor/ElementTypeVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/visitor/ElementTypeVisitor.java index 74a8621e3..eba413816 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/visitor/ElementTypeVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/visitor/ElementTypeVisitor.java @@ -18,6 +18,7 @@ package li.strolch.model.visitor; import li.strolch.model.Order; import li.strolch.model.Resource; import li.strolch.model.Tags; +import li.strolch.model.activity.Activity; /** * @author Robert von Burg @@ -33,4 +34,9 @@ public class ElementTypeVisitor implements StrolchRootElementVisitor { public String visitResource(Resource resource) { return Tags.RESOURCE; } + + @Override + public String visitActivity(Activity activity) { + return Tags.ACTIVITY; + } } diff --git a/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchRootElementVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchRootElementVisitor.java index d24cbebd9..3eab9ca85 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchRootElementVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchRootElementVisitor.java @@ -17,6 +17,7 @@ package li.strolch.model.visitor; import li.strolch.model.Order; import li.strolch.model.Resource; +import li.strolch.model.activity.Activity; /** * @author Robert von Burg @@ -26,4 +27,6 @@ public interface StrolchRootElementVisitor extends StrolchVisitor { public T visitOrder(Order order); public T visitResource(Resource resource); + + public T visitActivity(Activity activity); } diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlActivityDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlActivityDao.java index 29d26a401..5db385646 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlActivityDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlActivityDao.java @@ -21,8 +21,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLXML; +import java.sql.Timestamp; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import javax.xml.parsers.ParserConfigurationException; @@ -30,6 +32,9 @@ import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.sax.SAXResult; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + import li.strolch.model.Tags; import li.strolch.model.activity.Activity; import li.strolch.model.query.ActivityQuery; @@ -39,9 +44,6 @@ import li.strolch.model.xml.XmlModelSaxReader; import li.strolch.persistence.api.ActivityDao; import li.strolch.persistence.api.StrolchPersistenceException; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; - @SuppressWarnings("nls") public class PostgreSqlActivityDao extends PostgresqlDao implements ActivityDao { @@ -68,22 +70,22 @@ public class PostgreSqlActivityDao extends PostgresqlDao implements Ac SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(binaryStream, new XmlModelSaxReader(listener)); } catch (SQLException | IOException | SAXException | ParserConfigurationException e) { - throw new StrolchPersistenceException(MessageFormat.format( - "Failed to extract Activity from sqlxml value for {0} / {1}", id, type), e); + throw new StrolchPersistenceException( + MessageFormat.format("Failed to extract Activity from sqlxml value for {0} / {1}", id, type), e); } if (listener.getActivities().size() == 0) - throw new StrolchPersistenceException(MessageFormat.format( - "No Activity parsed from sqlxml value for {0} / {1}", id, type)); + throw new StrolchPersistenceException( + MessageFormat.format("No Activity parsed from sqlxml value for {0} / {1}", id, type)); if (listener.getActivities().size() > 1) - throw new StrolchPersistenceException(MessageFormat.format( - "Multiple Activities parsed from sqlxml value for {0} / {1}", id, type)); + throw new StrolchPersistenceException( + MessageFormat.format("Multiple Activities parsed from sqlxml value for {0} / {1}", id, type)); return listener.getActivities().get(0); } - protected SQLXML createSqlXml(Activity activity, PreparedStatement preparedStatement) throws SQLException, - SAXException { + protected SQLXML createSqlXml(Activity activity, PreparedStatement preparedStatement) + throws SQLException, SAXException { SQLXML sqlxml = tx().getConnection().createSQLXML(); SAXResult saxResult = sqlxml.setResult(SAXResult.class); ContentHandler contentHandler = saxResult.getHandler(); @@ -95,14 +97,29 @@ public class PostgreSqlActivityDao extends PostgresqlDao implements Ac @Override protected void internalSave(final Activity activity) { - String sql = "insert into " + getTableName() + " (id, name, type, asxml) values (?, ?, ?, ?)"; + + String sql = "insert into " + getTableName() + + " (id, version, created_by, created_at, deleted, latest, name, type, asxml) values (?, ?, ?, ?, ?, true, ?, ?, ?)"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // id preparedStatement.setString(1, activity.getId()); - preparedStatement.setString(2, activity.getName()); - preparedStatement.setString(3, activity.getType()); + + // version + preparedStatement.setInt(2, activity.getVersion().getVersion()); + preparedStatement.setString(3, activity.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(4, new Timestamp(activity.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(5, activity.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(6, activity.getName()); + preparedStatement.setString(7, activity.getType()); SQLXML sqlxml = createSqlXml(activity, preparedStatement); - preparedStatement.setSQLXML(4, sqlxml); + preparedStatement.setSQLXML(8, sqlxml); + try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { @@ -118,19 +135,79 @@ public class PostgreSqlActivityDao extends PostgresqlDao implements Ac throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Activity {0} due to {1}", activity.getLocator(), e.getLocalizedMessage()), e); } + + if (activity.getVersion().isFirstVersion()) { + return; + } + + // and set the previous version to not be latest anymore + sql = "update " + getTableName() + " SET latest = false WHERE id = ? AND version = ?"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // primary key + preparedStatement.setString(1, activity.getId()); + preparedStatement.setInt(2, activity.getVersion().getPreviousVersion()); + + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to update 1 previous element with id {0} and version {1} but SQL statement modified {2} elements!"; + msg = MessageFormat.format(msg, activity.getId(), activity.getVersion().getPreviousVersion(), modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException( + MessageFormat.format("Failed to update previous version of Activity {0} due to {1}", + activity.getVersion(), e.getLocalizedMessage()), + e); + } } @Override protected void internalUpdate(final Activity activity) { - String sql = "update " + getTableName() + " set name = ?, type = ?, asxml = ? where id = ? "; + + // with versioning we save a new object + if (tx().getRealm().isVersioningEnabled()) { + internalSave(activity); + return; + } + + // make sure is first version when versioning is not enabled + if (!activity.getVersion().isFirstVersion()) { + throw new StrolchPersistenceException(MessageFormat.format( + "Versioning is not enabled, so version must always be 0 to perform an update, but it is {0}", + activity.getVersion())); + } + + // and also not marked as deleted! + if (activity.getVersion().isDeleted()) { + throw new StrolchPersistenceException( + MessageFormat.format("Versioning is not enabled, so version can not be marked as deleted for {0}", + activity.getVersion())); + } + + String sql = "update " + getTableName() + + " set created_by = ?, created_at = ?, deleted = ?, latest = true, name = ?, type = ?, asxml = ? where id = ? and version = ?"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { - preparedStatement.setString(1, activity.getName()); - preparedStatement.setString(2, activity.getType()); - preparedStatement.setString(4, activity.getId()); + // version + preparedStatement.setString(1, activity.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(2, new Timestamp(activity.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(3, activity.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(4, activity.getName()); + preparedStatement.setString(5, activity.getType()); SQLXML sqlxml = createSqlXml(activity, preparedStatement); - preparedStatement.setSQLXML(3, sqlxml); + preparedStatement.setSQLXML(6, sqlxml); + + // primary key + preparedStatement.setString(7, activity.getId()); + preparedStatement.setInt(8, activity.getVersion().getVersion()); + try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java index fffcae303..48a7c86cc 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java @@ -45,7 +45,7 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.StrolchPersistenceException; @SuppressWarnings("nls") -public class PostgreSqlOrderDao extends PostgresqlXmlDao implements OrderDao { +public class PostgreSqlOrderDao extends PostgresqlDao implements OrderDao { public static final String ORDERS = "orders"; @@ -96,17 +96,31 @@ public class PostgreSqlOrderDao extends PostgresqlXmlDao implements Order @Override protected void internalSave(final Order order) { + String sql = "insert into " + getTableName() - + " (id, name, type, state, date, asxml) values (?, ?, ?, ?::order_state, ?, ?)"; + + " (id, version, created_by, created_at, deleted, latest, name, type, state, date, asxml) values (?, ?, ?, ?, ?, true, ?, ?, ?::order_state, ?, ?)"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // id preparedStatement.setString(1, order.getId()); - preparedStatement.setString(2, order.getName()); - preparedStatement.setString(3, order.getType()); - preparedStatement.setString(4, order.getState().name()); - preparedStatement.setTimestamp(5, new Timestamp(order.getDate().getTime()), Calendar.getInstance()); + + // version + preparedStatement.setInt(2, order.getVersion().getVersion()); + preparedStatement.setString(3, order.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(4, new Timestamp(order.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(5, order.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(6, order.getName()); + preparedStatement.setString(7, order.getType()); + preparedStatement.setString(8, order.getState().name()); + preparedStatement.setTimestamp(9, new Timestamp(order.getDate().getTime()), Calendar.getInstance()); SQLXML sqlxml = createSqlXml(order, preparedStatement); - preparedStatement.setSQLXML(6, sqlxml); + preparedStatement.setSQLXML(10, sqlxml); + try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { @@ -120,29 +134,87 @@ public class PostgreSqlOrderDao extends PostgresqlXmlDao implements Order } catch (SQLException | SAXException e) { throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Order {0} due to {1}", - order.getLocator(), e.getLocalizedMessage()), e); + order.getVersion(), e.getLocalizedMessage()), e); + } + + if (order.getVersion().isFirstVersion()) { + return; + } + + // and set the previous version to not be latest anymore + sql = "update " + getTableName() + " SET latest = false WHERE id = ? AND version = ?"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // primary key + preparedStatement.setString(1, order.getId()); + preparedStatement.setInt(2, order.getVersion().getPreviousVersion()); + + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to update 1 previous element with id {0} and version {1} but SQL statement modified {2} elements!"; + msg = MessageFormat.format(msg, order.getId(), order.getVersion().getPreviousVersion(), modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException( + MessageFormat.format("Failed to update previous version of Order {0} due to {1}", + order.getVersion(), e.getLocalizedMessage()), + e); } } @Override protected void internalUpdate(final Order order) { + + // with versioning we save a new object + if (tx().getRealm().isVersioningEnabled()) { + internalSave(order); + return; + } + + // make sure is first version when versioning is not enabled + if (!order.getVersion().isFirstVersion()) { + throw new StrolchPersistenceException(MessageFormat.format( + "Versioning is not enabled, so version must always be 0 to perform an update, but it is {0}", + order.getVersion())); + } + + // and also not marked as deleted! + if (order.getVersion().isDeleted()) { + throw new StrolchPersistenceException(MessageFormat.format( + "Versioning is not enabled, so version can not be marked as deleted for {0}", order.getVersion())); + } + + // now we update the existing object String sql = "update " + getTableName() - + " set name = ?, type = ?, state = ?::order_state, date = ?, asxml = ? where id = ? "; + + " set created_by = ?, created_at = ?, deleted = ?, latest = true, name = ?, type = ?, state = ?::order_state, date = ?, asxml = ? where id = ? and version = ?"; try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { - preparedStatement.setString(1, order.getName()); - preparedStatement.setString(2, order.getType()); - preparedStatement.setString(3, order.getState().name()); - preparedStatement.setTimestamp(4, new Timestamp(order.getDate().getTime()), Calendar.getInstance()); - preparedStatement.setString(6, order.getId()); + // version + preparedStatement.setString(1, order.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(2, new Timestamp(order.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(3, order.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(4, order.getName()); + preparedStatement.setString(5, order.getType()); + preparedStatement.setString(6, order.getState().name()); + preparedStatement.setTimestamp(7, new Timestamp(order.getDate().getTime()), Calendar.getInstance()); SQLXML sqlxml = createSqlXml(order, preparedStatement); - preparedStatement.setSQLXML(5, sqlxml); + preparedStatement.setSQLXML(8, sqlxml); + + // primary key + preparedStatement.setString(9, order.getId()); + preparedStatement.setInt(10, order.getVersion().getVersion()); + try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { - String msg = "Expected to update 1 element with id {0} but SQL statement modified {1} elements!"; - msg = MessageFormat.format(msg, order.getId(), modCount); + String msg = "Expected to update 1 element with id {0} and version {1} but SQL statement modified {2} elements!"; + msg = MessageFormat.format(msg, order.getId(), order.getVersion().getVersion(), modCount); throw new StrolchPersistenceException(msg); } } finally { diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java index df24584b1..7a6a683f3 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java @@ -100,7 +100,7 @@ public abstract class PostgreSqlQueryVisitor return this.sqlAsString; } - this.sql.append("type = ? AND\n"); + this.sql.append("type = ? AND latest = true AND\n"); this.sql.append(this.sb.toString()); appendOrdering(); diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java index c3cb71cbf..705eec5ae 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java @@ -21,8 +21,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLXML; +import java.sql.Timestamp; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import javax.xml.parsers.ParserConfigurationException; @@ -30,6 +32,9 @@ import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.sax.SAXResult; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + import li.strolch.model.Resource; import li.strolch.model.Tags; import li.strolch.model.query.ResourceQuery; @@ -39,9 +44,6 @@ import li.strolch.model.xml.XmlModelSaxReader; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchPersistenceException; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; - @SuppressWarnings("nls") public class PostgreSqlResourceDao extends PostgresqlDao implements ResourceDao { @@ -68,16 +70,16 @@ public class PostgreSqlResourceDao extends PostgresqlDao implements Re SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(binaryStream, new XmlModelSaxReader(listener)); } catch (SQLException | IOException | SAXException | ParserConfigurationException e) { - throw new StrolchPersistenceException(MessageFormat.format( - "Failed to extract Resource from sqlxml value for {0} / {1}", id, type), e); + throw new StrolchPersistenceException( + MessageFormat.format("Failed to extract Resource from sqlxml value for {0} / {1}", id, type), e); } if (listener.getResources().size() == 0) - throw new StrolchPersistenceException(MessageFormat.format( - "No Resource parsed from sqlxml value for {0} / {1}", id, type)); + throw new StrolchPersistenceException( + MessageFormat.format("No Resource parsed from sqlxml value for {0} / {1}", id, type)); if (listener.getResources().size() > 1) - throw new StrolchPersistenceException(MessageFormat.format( - "Multiple Resources parsed from sqlxml value for {0} / {1}", id, type)); + throw new StrolchPersistenceException( + MessageFormat.format("Multiple Resources parsed from sqlxml value for {0} / {1}", id, type)); return listener.getResources().get(0); } @@ -94,14 +96,26 @@ public class PostgreSqlResourceDao extends PostgresqlDao implements Re @Override protected void internalSave(final Resource res) { - String sql = "insert into " + getTableName() + " (id, name, type, asxml) values (?, ?, ?, ?)"; + String sql = "insert into " + getTableName() + + " (id, version, created_by, created_at, deleted, latest, name, type, asxml) values (?, ?, ?, ?, ?, true, ?, ?, ?)"; try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // id preparedStatement.setString(1, res.getId()); - preparedStatement.setString(2, res.getName()); - preparedStatement.setString(3, res.getType()); + + // version + preparedStatement.setInt(2, res.getVersion().getVersion()); + preparedStatement.setString(3, res.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(4, new Timestamp(res.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(5, res.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(6, res.getName()); + preparedStatement.setString(7, res.getType()); SQLXML sqlxml = createSqlXml(res, preparedStatement); - preparedStatement.setSQLXML(4, sqlxml); + preparedStatement.setSQLXML(8, sqlxml); try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { @@ -117,24 +131,82 @@ public class PostgreSqlResourceDao extends PostgresqlDao implements Re throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Resource {0} due to {1}", res.getLocator(), e.getLocalizedMessage()), e); } + + if (res.getVersion().isFirstVersion()) { + return; + } + + // and set the previous version to not be latest anymore + sql = "update " + getTableName() + " SET latest = false WHERE id = ? AND version = ?"; + try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { + + // primary key + preparedStatement.setString(1, res.getId()); + preparedStatement.setInt(2, res.getVersion().getPreviousVersion()); + + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to update 1 previous element with id {0} and version {1} but SQL statement modified {2} elements!"; + msg = MessageFormat.format(msg, res.getId(), res.getVersion().getPreviousVersion(), modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Resource {0} due to {1}", + res.getLocator(), e.getLocalizedMessage()), e); + } } @Override protected void internalUpdate(final Resource resource) { - String sql = "update " + getTableName() + " set name = ?, type = ?, asxml = ? where id = ? "; + + // with versioning we save a new object + if (tx().getRealm().isVersioningEnabled()) { + internalSave(resource); + return; + } + + // make sure is first version when versioning is not enabled + if (!resource.getVersion().isFirstVersion()) { + throw new StrolchPersistenceException(MessageFormat.format( + "Versioning is not enabled, so version must always be 0 to perform an update, but it is {0}", + resource.getVersion())); + } + + // and also not marked as deleted! + if (resource.getVersion().isDeleted()) { + throw new StrolchPersistenceException( + MessageFormat.format("Versioning is not enabled, so version can not be marked as deleted for {0}", + resource.getVersion())); + } + + // now we update the existing object + String sql = "update " + getTableName() + + " set created_by = ?, created_at = ?, deleted = ?, latest = true, name = ?, type = ?, asxml = ? where id = ? and version = ?"; try (PreparedStatement preparedStatement = tx().getConnection().prepareStatement(sql)) { - preparedStatement.setString(1, resource.getName()); - preparedStatement.setString(2, resource.getType()); - preparedStatement.setString(4, resource.getId()); + // version + preparedStatement.setString(1, resource.getVersion().getCreatedBy()); + preparedStatement.setTimestamp(2, new Timestamp(resource.getVersion().getCreatedAt().getTime()), + Calendar.getInstance()); + preparedStatement.setBoolean(3, resource.getVersion().isDeleted()); + + // attributes + preparedStatement.setString(4, resource.getName()); + preparedStatement.setString(5, resource.getType()); SQLXML sqlxml = createSqlXml(resource, preparedStatement); - preparedStatement.setSQLXML(3, sqlxml); + preparedStatement.setSQLXML(6, sqlxml); + + // primary key + preparedStatement.setString(7, resource.getId()); + preparedStatement.setInt(8, resource.getVersion().getVersion()); + try { int modCount = preparedStatement.executeUpdate(); if (modCount != 1) { - String msg = "Expected to update 1 element with id {0} but SQL statement modified {1} elements!"; - msg = MessageFormat.format(msg, resource.getId(), modCount); + String msg = "Expected to update 1 element with id {0} and version {1} but SQL statement modified {2} elements!"; + msg = MessageFormat.format(msg, resource.getId(), resource.getVersion().getVersion(), modCount); throw new StrolchPersistenceException(msg); } } finally { diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java index 7b0519849..b230ce54c 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java @@ -17,6 +17,9 @@ package li.strolch.persistence.postgresql; import java.sql.Connection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import li.strolch.agent.api.StrolchRealm; import li.strolch.persistence.api.AbstractTransaction; import li.strolch.persistence.api.ActivityDao; @@ -28,9 +31,6 @@ import li.strolch.persistence.api.TransactionResult; import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class PostgreSqlStrolchTransaction extends AbstractTransaction { private static final Logger logger = LoggerFactory.getLogger(PostgreSqlStrolchTransaction.class); @@ -56,6 +56,8 @@ public class PostgreSqlStrolchTransaction extends AbstractTransaction { this.orderDao.commit(txResult); if (this.resourceDao != null) this.resourceDao.commit(txResult); + if (this.activityDao != null) + this.activityDao.commit(txResult); // don't commit the connection, this is done in postCommit when we close the connection } diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java index d3f3121f8..5fe5a646f 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java @@ -33,7 +33,7 @@ import li.strolch.persistence.api.TransactionResult; @SuppressWarnings("nls") public abstract class PostgresqlDao implements StrolchDao { - protected PostgreSqlStrolchTransaction tx; + private PostgreSqlStrolchTransaction tx; protected List commands; public PostgresqlDao(PostgreSqlStrolchTransaction tx) { @@ -179,6 +179,18 @@ public abstract class PostgresqlDao implements Str } } + @Override + public T queryBy(String type, String id, int version) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List queryVersionsFor(String type, String id) { + // TODO Auto-generated method stub + return null; + } + @Override public List queryAll() { @@ -225,73 +237,55 @@ public abstract class PostgresqlDao implements Str @Override public void save(final T res) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - internalSave(res); - txResult.incCreated(1); - } + this.commands.add(txResult -> { + internalSave(res); + txResult.incCreated(1); }); } @Override public void saveAll(final List elements) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - for (T element : elements) { - internalSave(element); - } - txResult.incCreated(elements.size()); + this.commands.add(txResult -> { + for (T element : elements) { + internalSave(element); } + txResult.incCreated(elements.size()); }); } @Override public void update(final T element) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - internalUpdate(element); - txResult.incUpdated(1); - } + this.commands.add(txResult -> { + internalUpdate(element); + txResult.incUpdated(1); }); } @Override public void updateAll(final List elements) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - for (T element : elements) { - internalUpdate(element); - } - txResult.incUpdated(elements.size()); + this.commands.add(txResult -> { + for (T element : elements) { + internalUpdate(element); } + txResult.incUpdated(elements.size()); }); } @Override public void remove(final T element) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - internalRemove(element); - txResult.incDeleted(1); - } + this.commands.add(txResult -> { + internalRemove(element); + txResult.incDeleted(1); }); } @Override public void removeAll(final List elements) { - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - for (T element : elements) { - internalRemove(element); - } - txResult.incDeleted(elements.size()); + this.commands.add(txResult -> { + for (T element : elements) { + internalRemove(element); } + txResult.incDeleted(elements.size()); }); } @@ -300,12 +294,9 @@ public abstract class PostgresqlDao implements Str final long toRemove = querySize(); - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - internalRemoveAll(toRemove); - txResult.incDeleted(toRemove); - } + this.commands.add(txResult -> { + internalRemoveAll(toRemove); + txResult.incDeleted(toRemove); }); return toRemove; @@ -316,17 +307,20 @@ public abstract class PostgresqlDao implements Str final long toRemove = querySize(type); - this.commands.add(new DaoCommand() { - @Override - public void doComand(TransactionResult txResult) { - internalRemoveAllBy(toRemove, type); - txResult.incDeleted(toRemove); - } + this.commands.add(txResult -> { + internalRemoveAllBy(toRemove, type); + txResult.incDeleted(toRemove); }); return toRemove; } + @Override + public void removeVersion(T element) throws StrolchPersistenceException { + // TODO Auto-generated method stub + + } + /** * @param element */ @@ -366,8 +360,8 @@ public abstract class PostgresqlDao implements Str } } catch (SQLException e) { - throw new StrolchPersistenceException(MessageFormat.format("Failed to remove all elements due to {0}", - e.getLocalizedMessage()), e); + throw new StrolchPersistenceException( + MessageFormat.format("Failed to remove all elements due to {0}", e.getLocalizedMessage()), e); } } @@ -383,8 +377,8 @@ public abstract class PostgresqlDao implements Str } } catch (SQLException e) { - throw new StrolchPersistenceException(MessageFormat.format( - "Failed to remove all elements of type {0} due to {1}", type, e.getLocalizedMessage()), e); + throw new StrolchPersistenceException(MessageFormat + .format("Failed to remove all elements of type {0} due to {1}", type, e.getLocalizedMessage()), e); } } diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlXmlDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlXmlDao.java deleted file mode 100644 index c6557f40c..000000000 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlXmlDao.java +++ /dev/null @@ -1,14 +0,0 @@ -package li.strolch.persistence.postgresql; - -import java.sql.SQLXML; - -import li.strolch.model.StrolchRootElement; - -public abstract class PostgresqlXmlDao extends PostgresqlDao { - - public PostgresqlXmlDao(PostgreSqlStrolchTransaction tx) { - super(tx); - } - - protected abstract T parseFromXml(String id, String type, SQLXML xml); -} diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_drop.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_drop.sql new file mode 100644 index 000000000..0ff96e495 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_drop.sql @@ -0,0 +1,11 @@ + +DROP TABLE IF EXISTS resources; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS activities; + +DROP TABLE IF EXISTS audits; + +DROP TABLE IF EXISTS db_version; + +DROP TYPE IF EXISTS order_state; +DROP TYPE IF EXISTS access_type; diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_initial.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_initial.sql new file mode 100644 index 000000000..2a5aee503 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_initial.sql @@ -0,0 +1,131 @@ + +-- DB_VERSION +CREATE TABLE IF NOT EXISTS db_version ( + id serial primary key not null, + app varchar(255) not null, + version varchar(255) not null, + description varchar(255) not null, + created timestamp with time zone not null +); + +-- RESOURCES +CREATE TABLE IF NOT EXISTS resources ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + deleted boolean not null, + latest boolean not null, + name varchar(255) not null, + type varchar(255) not null, + asxml xml not null, + + PRIMARY KEY (id, version) +); + +-- ORDERS +CREATE TYPE order_state AS ENUM ('CREATED', 'OPEN', 'EXECUTION', 'CLOSED'); + +CREATE TABLE IF NOT EXISTS orders ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + deleted boolean, + latest boolean not null, + name varchar(255), + type varchar(255), + state order_state, + date timestamp with time zone, + asxml xml not null, + + PRIMARY KEY (id, version) +); + +-- ACTIVITIES +CREATE TABLE IF NOT EXISTS activities ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + deleted boolean not null, + latest boolean not null, + name varchar(255) not null, + type varchar(255) not null, + asxml xml not null, + + PRIMARY KEY (id, version) +); + +-- AUDITS +CREATE TYPE access_type AS ENUM ('READ', 'CREATE', 'UPDATE', 'DELETE'); +CREATE TABLE IF NOT EXISTS audits ( + id bigint PRIMARY KEY, + username varchar(255) NOT NULL, + firstname varchar(255) NOT NULL, + lastname varchar(255) NOT NULL, + date timestamp with time zone NOT NULL, + + element_type varchar(255) NOT NULL, + element_sub_type varchar(255) NOT NULL, + element_accessed varchar(255) NOT NULL, + new_version timestamp with time zone, + + action varchar(255) NOT NULL, + access_type access_type NOT NULL +); + +-- set version +INSERT INTO db_version + (version, app, description, created) +values( + '0.1.0', + 'strolch', + 'Initial schema version', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.2.0', + 'strolch', + 'Added new table for audits', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.2.1', + 'strolch', + 'Added new column app to table table version', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.3.0', + 'strolch', + 'Added new column element_sub_type to table audits', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.4.0', + 'strolch', + 'Added new table activities', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.5.0', + 'strolch', + 'Added versioning to root elements', + CURRENT_TIMESTAMP +); diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_migration.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_migration.sql new file mode 100644 index 000000000..0020f5c6b --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.5.0_migration.sql @@ -0,0 +1,72 @@ + +-- add version columns +ALTER TABLE resources ADD COLUMN version integer; +ALTER TABLE resources ADD COLUMN created_by varchar(255); +ALTER TABLE resources ADD COLUMN created_at timestamp with time zone; +ALTER TABLE resources ADD COLUMN deleted boolean; +ALTER TABLE resources ADD COLUMN latest boolean; + +ALTER TABLE orders ADD COLUMN version integer; +ALTER TABLE orders ADD COLUMN created_by varchar(255); +ALTER TABLE orders ADD COLUMN created_at timestamp with time zone; +ALTER TABLE orders ADD COLUMN deleted boolean; +ALTER TABLE orders ADD COLUMN latest boolean; + +ALTER TABLE activities ADD COLUMN version integer; +ALTER TABLE activities ADD COLUMN created_by varchar(255); +ALTER TABLE activities ADD COLUMN created_at timestamp with time zone; +ALTER TABLE activities ADD COLUMN deleted boolean; +ALTER TABLE activities ADD COLUMN latest boolean; + +-- set initial values for new columns +UPDATE resources SET version = 0 where version IS NULL; +UPDATE resources SET created_by = 'MIGRATION' where version IS NULL; +UPDATE resources SET created_at = CURRENT_TIMESTAMP where version IS NULL; +UPDATE resources SET deleted = false where version IS NULL; + +UPDATE orders SET version = 0 where version IS NULL; +UPDATE orders SET created_by = 'MIGRATION' where version IS NULL; +UPDATE orders SET created_at = CURRENT_TIMESTAMP where version IS NULL; +UPDATE orders SET deleted = false where version IS NULL; + +UPDATE activities SET version = 0 where version IS NULL; +UPDATE activities SET created_by = 'MIGRATION' where version IS NULL; +UPDATE activities SET created_at = CURRENT_TIMESTAMP where version IS NULL; +UPDATE activities SET deleted = false where version IS NULL; + +-- make columns not null +ALTER TABLE resources ALTER COLUMN version SET NOT NULL; +ALTER TABLE resources ALTER COLUMN created_by SET NOT NULL; +ALTER TABLE resources ALTER COLUMN created_at SET NOT NULL; +ALTER TABLE resources ALTER COLUMN latest SET NOT NULL; +ALTER TABLE resources ALTER COLUMN deleted SET NOT NULL; + +ALTER TABLE orders ALTER COLUMN version SET NOT NULL; +ALTER TABLE orders ALTER COLUMN created_by SET NOT NULL; +ALTER TABLE orders ALTER COLUMN created_at SET NOT NULL; +ALTER TABLE orders ALTER COLUMN latest SET NOT NULL; +ALTER TABLE orders ALTER COLUMN deleted SET NOT NULL; + +ALTER TABLE activities ALTER COLUMN version SET NOT NULL; +ALTER TABLE activities ALTER COLUMN created_by SET NOT NULL; +ALTER TABLE activities ALTER COLUMN created_at SET NOT NULL; +ALTER TABLE activities ALTER COLUMN latest SET NOT NULL; +ALTER TABLE activities ALTER COLUMN deleted SET NOT NULL; + +-- change primary key to id, version +ALTER TABLE resources DROP CONSTRAINT resources_pkey; +ALTER TABLE orders DROP CONSTRAINT orders_pkey; +ALTER TABLE activities DROP CONSTRAINT activities_pkey; + +ALTER TABLE resources ADD CONSTRAINT resources_pkey PRIMARY KEY (id, version); +ALTER TABLE orders ADD CONSTRAINT orders_pkey PRIMARY KEY (id, version); +ALTER TABLE activities ADD CONSTRAINT activities_pkey PRIMARY KEY (id, version); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.5.0', + 'strolch', + 'Added versioning to root elements', + CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties index 2560e0113..0361a7be0 100644 --- a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties @@ -1,2 +1,2 @@ # Property file defining what the currently expected version is supposed to be -db_version=0.4.0 +db_version=0.5.0 diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml index a3d133f42..61171509d 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml @@ -22,7 +22,7 @@ PrivilegeHandler PersistenceHandler - TRANSACTIONAL + CACHED true true diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java index 030613383..31d757707 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java @@ -22,6 +22,7 @@ import java.util.Set; import li.strolch.model.StrolchRootElement; import li.strolch.persistence.api.StrolchDao; +import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.xmlpers.api.PersistenceTransaction; import li.strolch.xmlpers.objref.IdOfSubTypeRef; @@ -104,6 +105,18 @@ public abstract class AbstractDao implements Strol return t; } + @Override + public T queryBy(String type, String id, int version) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List queryVersionsFor(String type, String id) { + // TODO Auto-generated method stub + return null; + } + @Override public List queryAll() { List objects = new ArrayList<>(); @@ -163,4 +176,10 @@ public abstract class AbstractDao implements Strol SubTypeRef typeRef = getTypeRef(type); return this.tx.getObjectDao().removeAllBy(typeRef); } + + @Override + public void removeVersion(T element) throws StrolchPersistenceException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("not yet implemented!"); + } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java index ab1140540..304f7267b 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java @@ -31,6 +31,7 @@ import li.strolch.utils.dbc.DBC; public class RemoveOrderCommand extends Command { private Order order; + private boolean removed; /** * @param tx @@ -65,14 +66,16 @@ public class RemoveOrderCommand extends Command { } orderMap.remove(tx(), this.order); + this.removed = true; } @Override public void undo() { - if (this.order != null && tx().isRollingBack()) { - OrderMap orderMap = tx().getOrderMap(); - if (!orderMap.hasElement(tx(), this.order.getType(), this.order.getId())) - orderMap.add(tx(), this.order); + if (this.order != null && tx().isRollingBack() && this.removed) { + if (tx().isVersioningEnabled()) + tx().getOrderMap().undoVersion(tx(), this.order); + else + tx().getOrderMap().add(tx(), this.order); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java index c392eceff..abcbefa43 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java @@ -31,6 +31,7 @@ import li.strolch.utils.dbc.DBC; public class RemoveResourceCommand extends Command { private Resource resource; + private boolean removed; /** * @param tx @@ -65,14 +66,16 @@ public class RemoveResourceCommand extends Command { } resourceMap.remove(tx(), this.resource); + this.removed = true; } @Override public void undo() { - if (this.resource != null && tx().isRollingBack()) { - ResourceMap resourceMap = tx().getResourceMap(); - if (!resourceMap.hasElement(tx(), this.resource.getType(), this.resource.getId())) - resourceMap.add(tx(), this.resource); + if (this.resource != null && tx().isRollingBack() && this.removed) { + if (tx().isVersioningEnabled()) + tx().getResourceMap().undoVersion(tx(), this.resource); + else + tx().getResourceMap().add(tx(), this.resource); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java index 2959f7089..eb6e1a34d 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java @@ -16,6 +16,7 @@ package li.strolch.command; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.List; import li.strolch.agent.api.ComponentContainer; @@ -32,7 +33,8 @@ import li.strolch.utils.dbc.DBC; public class UpdateOrderCollectionCommand extends Command { private List orders; - private List replacedElements; + private List replaced; + private boolean updated; /** * @param tx @@ -62,22 +64,32 @@ public class UpdateOrderCollectionCommand extends Command { } OrderMap orderMap = tx().getOrderMap(); + this.replaced = new ArrayList<>(); for (Order order : this.orders) { - if (!orderMap.hasElement(tx(), order.getType(), order.getId())) { + Order o = orderMap.getBy(tx(), order.getType(), order.getId()); + if (o == null) { String msg = "The Order {0} can not be updated as it does not exist!"; msg = MessageFormat.format(msg, order.getLocator()); throw new StrolchException(msg); } + + this.replaced.add(o); } - this.replacedElements = orderMap.updateAll(tx(), this.orders); + orderMap.updateAll(tx(), this.orders); + this.updated = true; } @Override public void undo() { - if (this.replacedElements != null && tx().isRollingBack()) { - OrderMap orderMap = tx().getOrderMap(); - orderMap.updateAll(tx(), this.replacedElements); + if (this.updated && tx().isRollingBack()) { + if (tx().isVersioningEnabled()) { + for (Order order : this.orders) { + tx().getOrderMap().undoVersion(tx(), order); + } + } else { + tx().getOrderMap().updateAll(tx(), this.replaced); + } } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java index 450570037..21ea838f1 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java @@ -31,7 +31,8 @@ import li.strolch.utils.dbc.DBC; public class UpdateOrderCommand extends Command { private Order order; - private Order replacedElement; + private Order replaced; + private boolean updated; /** * @param tx @@ -59,19 +60,24 @@ public class UpdateOrderCommand extends Command { tx().lock(this.order); OrderMap orderMap = tx().getOrderMap(); - if (!orderMap.hasElement(tx(), this.order.getType(), this.order.getId())) { - String msg = "The Order {0} can not be updated as it does not exist!"; + this.replaced = orderMap.getBy(tx(), this.order.getType(), this.order.getId()); + if (this.replaced == null) { + String msg = "The Order {0} can not be updated as it does not exist!!"; msg = MessageFormat.format(msg, this.order.getLocator()); throw new StrolchException(msg); } - this.replacedElement = orderMap.update(tx(), this.order); + orderMap.update(tx(), this.order); + this.updated = true; } @Override public void undo() { - if (this.replacedElement != null && tx().isRollingBack()) { - tx().getOrderMap().update(tx(), this.replacedElement); + if (this.updated && tx().isRollingBack()) { + if (tx().isVersioningEnabled()) + tx().getOrderMap().undoVersion(tx(), this.order); + else + tx().getOrderMap().update(tx(), this.replaced); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java index 5dc693af3..51aca9a80 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java @@ -16,6 +16,7 @@ package li.strolch.command; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.List; import li.strolch.agent.api.ComponentContainer; @@ -32,7 +33,8 @@ import li.strolch.utils.dbc.DBC; public class UpdateResourceCollectionCommand extends Command { private List resources; - private List replacedElements; + private List replaced; + private boolean updated; /** * @param tx @@ -62,22 +64,32 @@ public class UpdateResourceCollectionCommand extends Command { } ResourceMap resourceMap = tx().getResourceMap(); + this.replaced = new ArrayList<>(); for (Resource resource : this.resources) { - if (!resourceMap.hasElement(tx(), resource.getType(), resource.getId())) { + Resource r = resourceMap.getBy(tx(), resource.getType(), resource.getId()); + if (r == null) { String msg = "The Resource {0} can not be updated as it does not exist!"; msg = MessageFormat.format(msg, resource.getLocator()); throw new StrolchException(msg); } + + this.replaced.add(r); } - this.replacedElements = resourceMap.updateAll(tx(), this.resources); + resourceMap.updateAll(tx(), this.resources); + this.updated = true; } @Override public void undo() { - if (this.replacedElements != null && tx().isRollingBack()) { - ResourceMap resourceMap = tx().getResourceMap(); - resourceMap.updateAll(tx(), this.replacedElements); + if (this.updated && tx().isRollingBack()) { + if (tx().isVersioningEnabled()) { + for (Resource resource : this.resources) { + tx().getResourceMap().undoVersion(tx(), resource); + } + } else { + tx().getResourceMap().updateAll(tx(), this.replaced); + } } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java index e1638b582..49f6338aa 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java @@ -31,7 +31,8 @@ import li.strolch.utils.dbc.DBC; public class UpdateResourceCommand extends Command { private Resource resource; - private Resource replacedElement; + private Resource replaced; + private boolean updated; /** * @param tx @@ -59,19 +60,24 @@ public class UpdateResourceCommand extends Command { tx().lock(this.resource); ResourceMap resourceMap = tx().getResourceMap(); - if (!resourceMap.hasElement(tx(), this.resource.getType(), this.resource.getId())) { + this.replaced = resourceMap.getBy(tx(), this.resource.getType(), this.resource.getId()); + if (this.replaced == null) { String msg = "The Resource {0} can not be updated as it does not exist!!"; msg = MessageFormat.format(msg, this.resource.getLocator()); throw new StrolchException(msg); } - this.replacedElement = resourceMap.update(tx(), this.resource); + resourceMap.update(tx(), this.resource); + this.updated = true; } @Override public void undo() { - if (this.replacedElement != null && tx().isRollingBack()) { - tx().getResourceMap().update(tx(), this.replacedElement); + if (this.updated && tx().isRollingBack()) { + if (tx().isVersioningEnabled()) + tx().getResourceMap().undoVersion(tx(), this.resource); + else + tx().getResourceMap().update(tx(), this.replaced); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java index 1273e8720..1e0495a4c 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java @@ -18,6 +18,7 @@ package li.strolch.command.parameter; import java.text.MessageFormat; import li.strolch.agent.api.ComponentContainer; +import li.strolch.command.visitor.UndoUpdateElementVisitor; import li.strolch.command.visitor.UpdateElementVisitor; import li.strolch.model.ParameterizedElement; import li.strolch.model.StrolchRootElement; @@ -34,7 +35,6 @@ public class AddParameterCommand extends Command { private ParameterizedElement element; private Parameter parameter; - private StrolchRootElement replacedElement; /** * @param container @@ -79,7 +79,7 @@ public class AddParameterCommand extends Command { tx().lock(rootElement); this.element.addParameter(this.parameter); - this.replacedElement = new UpdateElementVisitor(tx()).update(rootElement); + new UpdateElementVisitor(tx()).update(rootElement); } @Override @@ -87,11 +87,8 @@ public class AddParameterCommand extends Command { if (this.parameter != null) { if (this.element.hasParameter(this.parameter.getId())) { this.element.removeParameter(this.parameter.getId()); + new UndoUpdateElementVisitor(tx()).undo(this.element.getRootElement()); } } - - if (this.replacedElement != null && this.element != this.replacedElement) { - new UpdateElementVisitor(tx()).update(this.replacedElement); - } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java index 2d44e3f5d..f3858267c 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java @@ -18,6 +18,7 @@ package li.strolch.command.parameter; import java.text.MessageFormat; import li.strolch.agent.api.ComponentContainer; +import li.strolch.command.visitor.UndoUpdateElementVisitor; import li.strolch.command.visitor.UpdateElementVisitor; import li.strolch.model.ParameterizedElement; import li.strolch.model.StrolchRootElement; @@ -36,7 +37,6 @@ public class RemoveParameterCommand extends Command { private String parameterId; private Parameter removedParameter; - private StrolchRootElement replacedElement; /** * @param container @@ -81,17 +81,14 @@ public class RemoveParameterCommand extends Command { tx().lock(rootElement); this.removedParameter = this.element.removeParameter(this.parameterId); - this.replacedElement = new UpdateElementVisitor(tx()).update(rootElement); + new UpdateElementVisitor(tx()).update(rootElement); } @Override public void undo() { if (this.removedParameter != null) { this.element.addParameter(this.removedParameter); - } - - if (this.replacedElement != null && this.element != this.replacedElement) { - new UpdateElementVisitor(tx()).update(this.replacedElement); + new UndoUpdateElementVisitor(tx()).undo(this.removedParameter.getRootElement()); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java index bcffd8189..421ee9ec9 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java @@ -16,6 +16,7 @@ package li.strolch.command.parameter; import li.strolch.agent.api.ComponentContainer; +import li.strolch.command.visitor.UndoUpdateElementVisitor; import li.strolch.command.visitor.UpdateElementVisitor; import li.strolch.model.StrolchRootElement; import li.strolch.model.parameter.Parameter; @@ -46,7 +47,7 @@ public class SetParameterCommand extends Command { private Integer oldIndex; private String oldValueAsString; - private StrolchRootElement replacedElement; + private boolean updated; /** * @param container @@ -151,7 +152,8 @@ public class SetParameterCommand extends Command { } if (hasChanges()) { - this.replacedElement = new UpdateElementVisitor(tx()).update(rootElement); + new UpdateElementVisitor(tx()).update(rootElement); + this.updated = true; } } @@ -185,8 +187,9 @@ public class SetParameterCommand extends Command { visitor.setValue(this.parameter, this.oldValueAsString); } - if (hasChanges() && this.replacedElement != null && this.replacedElement != this.parameter.getRootElement()) { - new UpdateElementVisitor(tx()).update(this.replacedElement); + if (hasChanges() && this.updated) { + StrolchRootElement rootElement = this.parameter.getRootElement(); + new UndoUpdateElementVisitor(tx()).undo(rootElement); } } } diff --git a/li.strolch.service/src/main/java/li/strolch/command/visitor/UndoUpdateElementVisitor.java b/li.strolch.service/src/main/java/li/strolch/command/visitor/UndoUpdateElementVisitor.java new file mode 100644 index 000000000..0d328a20e --- /dev/null +++ b/li.strolch.service/src/main/java/li/strolch/command/visitor/UndoUpdateElementVisitor.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.command.visitor; + +import li.strolch.model.Order; +import li.strolch.model.Resource; +import li.strolch.model.StrolchRootElement; +import li.strolch.model.activity.Activity; +import li.strolch.model.visitor.StrolchRootElementVisitor; +import li.strolch.persistence.api.StrolchTransaction; + +/** + * @author Robert von Burg + */ +public class UndoUpdateElementVisitor implements StrolchRootElementVisitor { + + private StrolchTransaction tx; + + public UndoUpdateElementVisitor(StrolchTransaction tx) { + this.tx = tx; + } + + public Void undo(StrolchRootElement rootElement) { + return rootElement.accept(this); + } + + @Override + public Void visitOrder(Order order) { + if (tx.isVersioningEnabled()) + this.tx.getOrderMap().undoVersion(this.tx, order); + else + this.tx.getOrderMap().update(this.tx, order); + return null; + } + + @Override + public Void visitResource(Resource resource) { + if (tx.isVersioningEnabled()) + this.tx.getResourceMap().undoVersion(this.tx, resource); + else + this.tx.getResourceMap().update(this.tx, resource); + return null; + } + + @Override + public Void visitActivity(Activity activity) { + if (tx.isVersioningEnabled()) + this.tx.getActivityMap().undoVersion(this.tx, activity); + else + this.tx.getActivityMap().update(this.tx, activity); + return null; + } +} diff --git a/li.strolch.service/src/main/java/li/strolch/command/visitor/UpdateElementVisitor.java b/li.strolch.service/src/main/java/li/strolch/command/visitor/UpdateElementVisitor.java index 347f98840..f9cf351a7 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/visitor/UpdateElementVisitor.java +++ b/li.strolch.service/src/main/java/li/strolch/command/visitor/UpdateElementVisitor.java @@ -18,13 +18,14 @@ package li.strolch.command.visitor; import li.strolch.model.Order; import li.strolch.model.Resource; import li.strolch.model.StrolchRootElement; +import li.strolch.model.activity.Activity; import li.strolch.model.visitor.StrolchRootElementVisitor; import li.strolch.persistence.api.StrolchTransaction; /** * @author Robert von Burg */ -public class UpdateElementVisitor implements StrolchRootElementVisitor { +public class UpdateElementVisitor implements StrolchRootElementVisitor { private StrolchTransaction tx; @@ -32,17 +33,25 @@ public class UpdateElementVisitor implements StrolchRootElementVisitor */ @@ -48,9 +49,6 @@ public abstract class AbstractRealmCommandTest { protected static Certificate certificate; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @BeforeClass public static void beforeClass() throws Exception { @@ -80,17 +78,27 @@ public abstract class AbstractRealmCommandTest { protected abstract Command getCommandInstance(ComponentContainer container, StrolchTransaction tx); - protected void doCommandAsFail(String realmName) { - this.expectedException.expect(RuntimeException.class); - this.expectedException.expectMessage("Fail on purpose after do command!"); + protected abstract void validateAfterCommand(ComponentContainer container, StrolchTransaction tx); + protected abstract void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx); + + protected void doCommandAsFail(String realmName) { StrolchRealm realm = runtimeMock.getContainer().getRealm(realmName); + boolean caught = false; try (StrolchTransaction tx = realm.openTx(certificate, "test")) { Command command = getCommandInstance(runtimeMock.getContainer(), tx); FailCommandFacade commandFacade = new FailCommandFacade(runtimeMock.getContainer(), tx, command); tx.addCommand(commandFacade); tx.commitOnClose(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), containsString("Fail on purpose after do command!")); + caught = true; + } + assertTrue(caught); + + try (StrolchTransaction tx = realm.openTx(certificate, "test")) { + validateAfterCommandFailed(runtimeMock.getContainer(), tx); } } @@ -101,21 +109,28 @@ public abstract class AbstractRealmCommandTest { tx.addCommand(command); tx.commitOnClose(); } + + try (StrolchTransaction tx = realm.openTx(certificate, "test")) { + validateAfterCommand(runtimeMock.getContainer(), tx); + } } @Test - public void shouldFailCommandTransient() { + public void shouldDoCommandTransient() { doCommandAsFail(REALM_TRANSIENT); + doCommand(REALM_TRANSIENT); } @Test - public void shouldFailCommandCached() { + public void shouldDoCommandCached() { doCommandAsFail(REALM_CACHED); + doCommand(REALM_CACHED); } @Test - public void shouldFailCommandTransactional() { + public void shouldDoCommandTransactional() { doCommandAsFail(REALM_TRANSACTIONAL); + doCommand(REALM_TRANSACTIONAL); } private class FailCommandFacade extends Command { diff --git a/li.strolch.service/src/test/java/li/strolch/command/AddOrderCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/AddOrderCollectionCommandTest.java index 8c1a700d8..a578bab7b 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/AddOrderCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/AddOrderCollectionCommandTest.java @@ -15,17 +15,20 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -48,4 +51,18 @@ public class AddOrderCollectionCommandTest extends AbstractRealmCommandTest { command.setOrders(this.orders); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Order order : orders) { + assertTrue(tx.getOrderMap().hasElement(tx, order.getType(), order.getId())); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Order order : orders) { + assertFalse(tx.getOrderMap().hasElement(tx, order.getType(), order.getId())); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/AddOrderCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/AddOrderCommandTest.java index b5c1f9e4a..a17e15813 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/AddOrderCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/AddOrderCommandTest.java @@ -15,14 +15,17 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -42,4 +45,14 @@ public class AddOrderCommandTest extends AbstractRealmCommandTest { command.setOrder(this.order); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + assertTrue(tx.getOrderMap().hasElement(tx, this.order.getType(), this.order.getId())); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + assertFalse(tx.getOrderMap().hasElement(tx, this.order.getType(), this.order.getId())); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/AddResourceCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/AddResourceCollectionCommandTest.java index 6ec764e60..021dfa201 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/AddResourceCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/AddResourceCollectionCommandTest.java @@ -15,17 +15,20 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -48,4 +51,18 @@ public class AddResourceCollectionCommandTest extends AbstractRealmCommandTest { command.setResources(this.resources); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Resource resource : resources) { + assertTrue(tx.getResourceMap().hasElement(tx, resource.getType(), resource.getId())); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Resource resource : resources) { + assertFalse(tx.getResourceMap().hasElement(tx, resource.getType(), resource.getId())); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/AddResourceCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/AddResourceCommandTest.java index fde80a188..5c799289b 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/AddResourceCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/AddResourceCommandTest.java @@ -15,14 +15,17 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -42,4 +45,14 @@ public class AddResourceCommandTest extends AbstractRealmCommandTest { command.setResource(this.resource); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + assertTrue(tx.getResourceMap().hasElement(tx, this.resource.getType(), this.resource.getId())); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + assertFalse(tx.getResourceMap().hasElement(tx, this.resource.getType(), this.resource.getId())); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCollectionCommandTest.java index d8ee42a12..561d22847 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCollectionCommandTest.java @@ -15,9 +15,14 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Locator; import li.strolch.model.Order; @@ -25,8 +30,6 @@ import li.strolch.model.Tags; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -54,4 +57,18 @@ public class RemoveOrderCollectionCommandTest extends AbstractRealmCommandTest { command.setOrders(orders); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Locator locator : locators) { + assertFalse(tx.getOrderMap().hasElement(tx, locator.get(1), locator.get(2))); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Locator locator : locators) { + assertTrue(tx.getOrderMap().hasElement(tx, locator.get(1), locator.get(2))); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCommandTest.java index bfd9ade35..8643c462b 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/RemoveOrderCommandTest.java @@ -15,6 +15,11 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Locator; import li.strolch.model.Order; @@ -22,8 +27,6 @@ import li.strolch.model.Tags; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -45,4 +48,14 @@ public class RemoveOrderCommandTest extends AbstractRealmCommandTest { command.setOrder(order); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + assertFalse(tx.getOrderMap().hasElement(tx, "TestType", "@3")); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + assertTrue(tx.getOrderMap().hasElement(tx, "TestType", "@3")); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCollectionCommandTest.java index 9df74efd8..33bb8cb44 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCollectionCommandTest.java @@ -15,9 +15,14 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Locator; import li.strolch.model.Resource; @@ -25,8 +30,6 @@ import li.strolch.model.Tags; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -54,4 +57,18 @@ public class RemoveResourceCollectionCommandTest extends AbstractRealmCommandTes command.setResources(resources); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Locator locator : locators) { + assertFalse(tx.getResourceMap().hasElement(tx, locator.get(1), locator.get(2))); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Locator locator : locators) { + assertTrue(tx.getResourceMap().hasElement(tx, locator.get(1), locator.get(2))); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCommandTest.java index 6731f2e3f..fa3ce11b5 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/RemoveResourceCommandTest.java @@ -15,6 +15,11 @@ */ package li.strolch.command; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Locator; import li.strolch.model.Resource; @@ -22,8 +27,6 @@ import li.strolch.model.Tags; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -45,4 +48,14 @@ public class RemoveResourceCommandTest extends AbstractRealmCommandTest { command.setResource(resource); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + assertFalse(tx.getResourceMap().hasElement(tx, "Enumeration", "sex")); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + assertTrue(tx.getResourceMap().hasElement(tx, "Enumeration", "sex")); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCollectionCommandTest.java index f05219655..891b87f89 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCollectionCommandTest.java @@ -15,17 +15,19 @@ */ package li.strolch.command; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -36,6 +38,7 @@ public class UpdateOrderCollectionCommandTest extends AbstractRealmCommandTest { @Before public void before() { this.orders = new ArrayList<>(); + // we create elements with the same id as already exists! this.orders.add(ModelGenerator.createOrder("@1", "Modified Test Order", "TestType")); this.orders.add(ModelGenerator.createOrder("@2", "Modified Test Order", "TestType")); this.orders.add(ModelGenerator.createOrder("@3", "Modified Test Order", "TestType")); @@ -48,4 +51,20 @@ public class UpdateOrderCollectionCommandTest extends AbstractRealmCommandTest { command.setOrders(this.orders); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Order order : orders) { + Order o = tx.getOrderBy(order.getType(), order.getId()); + assertEquals("Modified Test Order", o.getName()); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Order order : orders) { + Order o = tx.getOrderBy(order.getType(), order.getId()); + assertEquals("Test Name", o.getName()); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCommandTest.java index 0bc9c40c9..35b6b35fe 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/UpdateOrderCommandTest.java @@ -42,4 +42,16 @@ public class UpdateOrderCommandTest extends AbstractRealmCommandTest { command.setOrder(this.order); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + // TODO Auto-generated method stub + + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + // TODO Auto-generated method stub + + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCollectionCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCollectionCommandTest.java index cfaaa636d..b0d958ba6 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCollectionCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCollectionCommandTest.java @@ -15,17 +15,19 @@ */ package li.strolch.command; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.List; +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.model.ModelGenerator; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -48,4 +50,20 @@ public class UpdateResourceCollectionCommandTest extends AbstractRealmCommandTes command.setResources(this.resources); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + for (Resource resource : this.resources) { + Resource r = tx.getResourceBy(resource.getType(), resource.getId()); + assertEquals("Modified Enumeration", r.getName()); + } + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + for (Resource resource : this.resources) { + Resource r = tx.getResourceBy(resource.getType(), resource.getId()); + assertEquals(r.getId(), r.getName().toLowerCase()); + } + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCommandTest.java index a144a62aa..ed42d77ee 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/UpdateResourceCommandTest.java @@ -42,4 +42,16 @@ public class UpdateResourceCommandTest extends AbstractRealmCommandTest { command.setResource(this.resource); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + // TODO Auto-generated method stub + + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + // TODO Auto-generated method stub + + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/parameter/AddParameterCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/parameter/AddParameterCommandTest.java index 7689a162e..c0f3e6e53 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/parameter/AddParameterCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/parameter/AddParameterCommandTest.java @@ -15,6 +15,11 @@ */ package li.strolch.command.parameter; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.command.AbstractRealmCommandTest; import li.strolch.model.Locator; @@ -24,8 +29,6 @@ import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -50,4 +53,16 @@ public class AddParameterCommandTest extends AbstractRealmCommandTest { command.setParameter(this.parameter); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + ParameterizedElement element = tx.findElement(this.locator); + assertTrue(element.hasParameter(this.parameter.getId())); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + ParameterizedElement element = tx.findElement(this.locator); + assertFalse(element.hasParameter(this.parameter.getId())); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/parameter/RemoveParameterCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/parameter/RemoveParameterCommandTest.java index 060f3c60c..9f3822c23 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/parameter/RemoveParameterCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/parameter/RemoveParameterCommandTest.java @@ -15,6 +15,11 @@ */ package li.strolch.command.parameter; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.command.AbstractRealmCommandTest; import li.strolch.model.Locator; @@ -22,8 +27,6 @@ import li.strolch.model.ParameterizedElement; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -48,4 +51,16 @@ public class RemoveParameterCommandTest extends AbstractRealmCommandTest { command.setParameterId(this.parameterId); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + ParameterizedElement element = tx.findElement(this.locator); + assertFalse(element.hasParameter(this.parameterId)); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + ParameterizedElement element = tx.findElement(this.locator); + assertTrue(element.hasParameter(this.parameterId)); + } } diff --git a/li.strolch.service/src/test/java/li/strolch/command/parameter/SetParameterCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/parameter/SetParameterCommandTest.java index 0562101f5..f78c0d39e 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/parameter/SetParameterCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/parameter/SetParameterCommandTest.java @@ -15,6 +15,10 @@ */ package li.strolch.command.parameter; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; + import li.strolch.agent.api.ComponentContainer; import li.strolch.command.AbstractRealmCommandTest; import li.strolch.model.Locator; @@ -22,8 +26,6 @@ import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import org.junit.Before; - /** * @author Robert von Burg */ @@ -31,6 +33,7 @@ public class SetParameterCommandTest extends AbstractRealmCommandTest { private Locator locator; private String valueAsString; + private String originalValue; @Before public void before() { @@ -42,10 +45,23 @@ public class SetParameterCommandTest extends AbstractRealmCommandTest { protected Command getCommandInstance(ComponentContainer container, StrolchTransaction tx) { Parameter parameter = tx.findElement(this.locator); + this.originalValue = parameter.getValueAsString(); SetParameterCommand command = new SetParameterCommand(container, tx); command.setValueAsString(this.valueAsString); command.setParameter(parameter); return command; } + + @Override + protected void validateAfterCommand(ComponentContainer container, StrolchTransaction tx) { + Parameter parameter = tx.findElement(this.locator); + assertEquals(this.valueAsString, parameter.getValueAsString()); + } + + @Override + protected void validateAfterCommandFailed(ComponentContainer container, StrolchTransaction tx) { + Parameter parameter = tx.findElement(this.locator); + assertEquals(this.originalValue, parameter.getValueAsString()); + } } diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java index 6abb15e7f..d5b391501 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java @@ -15,15 +15,15 @@ */ package li.strolch.testbase.runtime; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public abstract class AbstractModelTest { protected static final Logger logger = LoggerFactory.getLogger(AbstractModelTest.class); @@ -97,9 +97,43 @@ public abstract class AbstractModelTest { testRunner.runBulkOperationTests(); } + @Test + public void shouldCreateActivities() { + + ActivityModelTestRunner testRunner = new ActivityModelTestRunner(getRuntimeMock(), this.realmName); + testRunner.runCreateActivityTest(); + } + + @Test + public void shouldQueryActivitySizes() { + + ActivityModelTestRunner testRunner = new ActivityModelTestRunner(getRuntimeMock(), this.realmName); + testRunner.runQuerySizeTest(); + } + + @Test + public void shouldActivityCrud() { + + ActivityModelTestRunner testRunner = new ActivityModelTestRunner(getRuntimeMock(), this.realmName); + testRunner.runCrudTests(); + } + + @Test + public void shouldActivityPerformBulkOperations() { + + ActivityModelTestRunner testRunner = new ActivityModelTestRunner(getRuntimeMock(), this.realmName); + testRunner.runBulkOperationTests(); + } + @Test public void shouldTestAudits() { AuditModelTestRunner testRunner = new AuditModelTestRunner(getRuntimeMock(), this.realmName); testRunner.runTestForAudits(); } + + @Test + public void shouldTestVersioning() { + VersioningTestRunner testRunner = new VersioningTestRunner(getRuntimeMock()); + testRunner.runTestsForVersioning(); + } } diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ActivityModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ActivityModelTestRunner.java new file mode 100644 index 000000000..7d89694e0 --- /dev/null +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ActivityModelTestRunner.java @@ -0,0 +1,276 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.testbase.runtime; + +import static li.strolch.model.ModelGenerator.BAG_ID; +import static li.strolch.model.ModelGenerator.PARAM_STRING_ID; +import static li.strolch.model.ModelGenerator.createActivities; +import static li.strolch.model.ModelGenerator.createActivity; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import li.strolch.agent.api.ActivityMap; +import li.strolch.agent.impl.DataStoreMode; +import li.strolch.model.activity.Activity; +import li.strolch.model.parameter.Parameter; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.runtime.privilege.PrivilegeHandler; + +@SuppressWarnings("nls") +public class ActivityModelTestRunner { + + private static final String ID = "@testActivity"; + private static final String NAME = "Test Activity"; + private static final String TYPE = "Produce"; + + private RuntimeMock runtimeMock; + private String realmName; + private Certificate certificate; + + public ActivityModelTestRunner(RuntimeMock runtimeMock, String realmName) { + this.runtimeMock = runtimeMock; + this.realmName = realmName; + + PrivilegeHandler privilegeHandler = runtimeMock.getContainer().getPrivilegeHandler(); + this.certificate = privilegeHandler.authenticate("test", "test".getBytes()); + } + + public void runCreateActivityTest() { + + // create + Activity newActivity = createActivity("MyTestActivity", "Test Name", "TestType"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().add(tx, newActivity); + tx.commitOnClose(); + } + } + + public void runQuerySizeTest() { + + // remove all + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().removeAll(tx, tx.getActivityMap().getAllElements(tx)); + tx.commitOnClose(); + } + + // create three activities + Activity activity1 = createActivity("myTestActivity1", "Test Name", "QTestType1"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + Activity activity2 = createActivity("myTestActivity2", "Test Name", "QTestType2"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + Activity activity3 = createActivity("myTestActivity3", "Test Name", "QTestType3"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().add(tx, activity1); + tx.getActivityMap().add(tx, activity2); + tx.getActivityMap().add(tx, activity3); + tx.commitOnClose(); + } + + // query size + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + long size = tx.getActivityMap().querySize(tx); + assertEquals("Should have three objects", 3, size); + + size = tx.getActivityMap().querySize(tx, "QTestType1"); + assertEquals("Should have only one object of type 'QTestType1'", 1, size); + + size = tx.getActivityMap().querySize(tx, "QTestType2"); + assertEquals("Should have only one object of type 'QTestType1'", 1, size); + + size = tx.getActivityMap().querySize(tx, "QTestType3"); + assertEquals("Should have only one object of type 'QTestType1'", 1, size); + + size = tx.getActivityMap().querySize(tx, "NonExistingType"); + assertEquals("Should have zero objects of type 'NonExistingType'", 0, size); + } + } + + public void runCrudTests() { + + // create + Activity newActivity = createActivity(ID, NAME, TYPE); + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().add(tx, newActivity); + tx.commitOnClose(); + } + + // read + Activity readActivity = null; + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + readActivity = tx.getActivityMap().getBy(tx, TYPE, ID); + } + assertNotNull("Should read Activity with id " + ID, readActivity); + + // update + Parameter sParam = readActivity.getParameter(BAG_ID, PARAM_STRING_ID); + String newStringValue = "Giddiya!"; + sParam.setValue(newStringValue); + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().update(tx, readActivity); + tx.commitOnClose(); + } + + // read updated + Activity updatedActivity = null; + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + updatedActivity = tx.getActivityMap().getBy(tx, TYPE, ID); + } + assertNotNull("Should read Activity with id " + ID, updatedActivity); + if (this.runtimeMock.getRealm(this.realmName).getMode() != DataStoreMode.CACHED) + assertFalse("Objects can't be the same reference after re-reading!", readActivity == updatedActivity); + Parameter updatedParam = readActivity.getParameter(BAG_ID, PARAM_STRING_ID); + assertEquals(newStringValue, updatedParam.getValue()); + + // delete + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + tx.getActivityMap().remove(tx, readActivity); + tx.commitOnClose(); + } + + // fail to re-read + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test");) { + Activity activity = tx.getActivityMap().getBy(tx, TYPE, ID); + assertNull("Should no read Activity with id " + ID, activity); + } + } + + public void runBulkOperationTests() { + + // create 15 activities + List activities = new ArrayList<>(); + activities.addAll(createActivities(activities.size(), 5, "@", "My Activity", "MyType1")); + activities.addAll(createActivities(activities.size(), 5, "@", "Other Activity", "MyType2")); + activities.addAll(createActivities(activities.size(), 5, "@", "Further Activity", "MyType3")); + + // sort them so we know which activity our objects are + Comparator comparator = new Comparator() { + @Override + public int compare(Activity o1, Activity o2) { + return o1.getId().compareTo(o2.getId()); + } + }; + Collections.sort(activities, comparator); + + // first clear the map, so that we have a clean state + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + activityMap.removeAll(tx, activityMap.getAllElements(tx)); + tx.commitOnClose(); + } + + { + // make sure it is empty + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + assertEquals(0, activityMap.querySize(tx)); + } + + // now add some activities + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + tx.getActivityMap().addAll(tx, activities); + tx.commitOnClose(); + } + + // make sure we have our expected size + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + assertEquals(activities.size(), activityMap.querySize(tx)); + assertEquals(5, activityMap.querySize(tx, "MyType3")); + } + + // now use the remove all by type + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + tx.getActivityMap().removeAllBy(tx, "MyType3"); + tx.commitOnClose(); + } + + // again make sure we have our expected size + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + assertEquals(activities.size() - 5, activityMap.querySize(tx)); + assertEquals(0, activityMap.querySize(tx, "MyType3")); + } + + // now use the remove all + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + long removed = tx.getActivityMap().removeAll(tx); + assertEquals(activities.size() - 5, removed); + tx.commitOnClose(); + } + + // again make sure we have our expected size + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + assertEquals(0, activityMap.querySize(tx)); + } + } + + // remove the version + activities.forEach(t -> t.setVersion(null)); + + // now add all again + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + tx.getActivityMap().addAll(tx, activities); + tx.commitOnClose(); + } + + Set expectedTypes = new HashSet<>(); + expectedTypes.add("MyType1"); + expectedTypes.add("MyType2"); + expectedTypes.add("MyType3"); + + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + List allActivities = tx.getActivityMap().getAllElements(tx); + Collections.sort(allActivities, comparator); + assertEquals(activities, allActivities); + } + + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + ActivityMap activityMap = tx.getActivityMap(); + + Set types = activityMap.getTypes(tx); + assertEquals(expectedTypes, types); + + Set keySet = activityMap.getAllKeys(tx); + assertEquals(15, keySet.size()); + + for (String type : types) { + Set idsByType = activityMap.getKeysBy(tx, type); + assertEquals(5, idsByType.size()); + + List activitiesByType = activityMap.getElementsBy(tx, type); + assertEquals(5, activitiesByType.size()); + } + } + + try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { + Activity activity = tx.getActivityMap().getBy(tx, "MyType1", "@00000001"); + assertNotNull(activity); + activity = tx.getActivityMap().getBy(tx, "MyType2", "@00000006"); + assertNotNull(activity); + activity = tx.getActivityMap().getBy(tx, "MyType3", "@00000011"); + assertNotNull(activity); + } + } +} diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java index ffd2af630..93bfc11c0 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java @@ -226,6 +226,9 @@ public class OrderModelTestRunner { } } + // remove the version + orders.forEach(t -> t.setVersion(null)); + // now add all again try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { tx.getOrderMap().addAll(tx, orders); diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java index fa259f29b..1c81dd0c7 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java @@ -226,6 +226,8 @@ public class ResourceModelTestRunner { } } + resources.forEach(t -> t.setVersion(null)); + // now add all again try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName).openTx(this.certificate, "test")) { tx.getResourceMap().addAll(tx, resources); diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/VersioningTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/VersioningTestRunner.java new file mode 100644 index 000000000..eccac6a75 --- /dev/null +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/VersioningTestRunner.java @@ -0,0 +1,195 @@ +package li.strolch.testbase.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import li.strolch.agent.api.ComponentContainer; +import li.strolch.model.ModelGenerator; +import li.strolch.model.Resource; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.runtime.privilege.PrivilegeHandler; + +public class VersioningTestRunner { + + private RuntimeMock runtimeMock; + private Certificate certificate; + + public VersioningTestRunner(RuntimeMock runtimeMock) { + this.runtimeMock = runtimeMock; + + PrivilegeHandler privilegeHandler = runtimeMock.getContainer().getPrivilegeHandler(); + this.certificate = privilegeHandler.authenticate("test", "test".getBytes()); + } + + public void runTestsForVersioning() { + + ComponentContainer container = runtimeMock.getContainer(); + + // initialize by adding a resource + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = ModelGenerator.createResource("MyTestResource", "Test Name", "TestType"); + tx.getResourceMap().add(tx, res1); + // must be first version + assertEquals(0, res1.getVersion().getVersion()); + tx.commitOnClose(); + } + + // first make sure that the we can't change anything without updating the model + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + // must be first version + assertEquals(0, res1.getVersion().getVersion()); + res1.setName("Something"); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals("Test Name", res1.getName()); + + tx.commitOnClose(); + } + + // now do a change + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + res1.setName("Something"); + tx.getResourceMap().update(tx, res1); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals("Something", res1.getName()); + // version must be incremented + assertEquals(1, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + + // now revert the change + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource revertedVersion = tx.getResourceMap().revertToVersion(tx, "TestType", "MyTestResource", 0); + assertEquals("Test Name", revertedVersion.getName()); + // version must be incremented + assertEquals(2, revertedVersion.getVersion().getVersion()); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals("Test Name", res1.getName()); + // version must be incremented + assertEquals(2, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + + // undo a version in same TX + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + + // create a new version: + res1.setName("Version 3"); + tx.getResourceMap().update(tx, res1); + // version must be incremented + assertEquals(3, res1.getVersion().getVersion()); + + // now undo + tx.getResourceMap().undoVersion(tx, res1); + + // and validate we have again version 2 + res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals("Test Name", res1.getName()); + assertEquals(2, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals("Test Name", res1.getName()); + // version must be incremented + assertEquals(2, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + + // undo all versions + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1; + + // undo three times as we have version 0, 1, 2 + res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals(2, res1.getVersion().getVersion()); + tx.getResourceMap().undoVersion(tx, res1); + + res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals(1, res1.getVersion().getVersion()); + tx.getResourceMap().undoVersion(tx, res1); + + res1 = tx.getResourceBy("TestType", "MyTestResource", true); + assertEquals(0, res1.getVersion().getVersion()); + tx.getResourceMap().undoVersion(tx, res1); + + // and validate all are deleted + assertFalse(tx.getResourceMap().hasElement(tx, "TestType", "MyTestResource")); + + tx.commitOnClose(); + } + + // do a deletion + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = ModelGenerator.createResource("ball", "Red Ball", "Ball"); + assertNull(res1.getVersion()); + + tx.getResourceMap().add(tx, res1); + assertEquals(0, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("Ball", "ball", true); + assertEquals("Red Ball", res1.getName()); + + tx.getResourceMap().remove(tx, res1); + // version must be incremented + assertEquals(1, res1.getVersion().getVersion()); + + res1 = tx.getResourceBy("Ball", "ball"); + assertNull(res1); + + assertFalse(tx.getResourceMap().hasElement(tx, "Ball", "ball")); + + tx.commitOnClose(); + } + + // restore a version manually + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("Ball", "ball"); + assertNull(res1); + + List versions = tx.getResourceMap().getVersionsFor(tx, "Ball", "ball"); + assertEquals(2, versions.size()); + + res1 = versions.get(versions.size() - 1); + assertTrue(res1.getVersion().isDeleted()); + tx.getResourceMap().add(tx, res1); + assertEquals(2, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + try (StrolchTransaction tx = container.getRealm(certificate).openTx(certificate, VersioningTestRunner.class)) { + Resource res1 = tx.getResourceBy("Ball", "ball"); + assertNotNull(res1); + assertEquals(2, res1.getVersion().getVersion()); + + tx.commitOnClose(); + } + } +}