details:   https://code.openbravo.com/erp/devel/pi/rev/ebd6611474d3
changeset: 35216:ebd6611474d3
user:      Asier Lostalé <asier.lostale <at> openbravo.com>
date:      Tue Dec 11 15:27:05 2018 +0100
summary:   related to issue 39672: improved javadoc

details:   https://code.openbravo.com/erp/devel/pi/rev/7e4c318ad6a3
changeset: 35217:7e4c318ad6a3
user:      Asier Lostalé <asier.lostale <at> openbravo.com>
date:      Tue Dec 11 15:37:30 2018 +0100
summary:   related to issue 39672: added test cases

diffstat:

 src-test/src/org/openbravo/test/AllAntTaskTests.java    |    4 +-
 src-test/src/org/openbravo/test/dal/DalLockingTest.java |  223 ++++++++++++++++
 src/org/openbravo/dal/service/OBDal.java                |   31 +-
 3 files changed, 248 insertions(+), 10 deletions(-)

diffs (truncated from 313 to 300 lines):

diff -r 4c4039a9c917 -r 7e4c318ad6a3 
src-test/src/org/openbravo/test/AllAntTaskTests.java
--- a/src-test/src/org/openbravo/test/AllAntTaskTests.java      Tue Dec 11 
12:26:44 2018 +0000
+++ b/src-test/src/org/openbravo/test/AllAntTaskTests.java      Tue Dec 11 
15:37:30 2018 +0100
@@ -50,6 +50,7 @@
 import org.openbravo.test.dal.DalComplexQueryRequisitionTest;
 import org.openbravo.test.dal.DalComplexQueryTestOrderLine;
 import org.openbravo.test.dal.DalConnectionProviderTest;
+import org.openbravo.test.dal.DalLockingTest;
 import org.openbravo.test.dal.DalPerformanceInventoryLineTest;
 import org.openbravo.test.dal.DalPerformanceProductTest;
 import org.openbravo.test.dal.DalPerformanceProxyTest;
@@ -151,6 +152,7 @@
     DalPerformanceProxyTest.class, //
     DalQueryTest.class, //
     DalTest.class, //
+    DalLockingTest.class, //
     CentralBrokerTest.class, //
     DalUtilTest.class, //
     IssuesTest.class, //
@@ -317,7 +319,7 @@
 
     // AD_Org Persist Information
     ADOrgPersistInfoTestSuite.class,
-    
+
     // Automatic Invoice from Goods Shipment
     InvoiceFromShipmentTest.class
 
diff -r 4c4039a9c917 -r 7e4c318ad6a3 
src-test/src/org/openbravo/test/dal/DalLockingTest.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/test/dal/DalLockingTest.java   Tue Dec 11 
15:37:30 2018 +0100
@@ -0,0 +1,223 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2018 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.test.dal;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.LazyInitializationException;
+import org.hibernate.LockMode;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.openbravo.base.exception.OBException;
+import org.openbravo.base.provider.OBProvider;
+import org.openbravo.dal.core.OBContext;
+import org.openbravo.dal.service.OBDal;
+import org.openbravo.model.ad.alert.AlertRecipient;
+import org.openbravo.model.ad.alert.AlertRule;
+import org.openbravo.test.base.OBBaseTest;
+
+/**
+ * Test cases covering
+ * {@link 
OBDal#getObjectLockForNoKeyUpdate(org.openbravo.base.structure.BaseOBObject)}
+ */
+public class DalLockingTest extends OBBaseTest {
+  private static final Logger log = LogManager.getLogger();
+  private static String testingRuleId;
+
+  private List<String> executionOrder;
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @BeforeClass
+  public static void createTestEnvironment() {
+    OBContext.setAdminMode();
+    try {
+      AlertRule newAlertRule = OBProvider.getInstance().get(AlertRule.class);
+      newAlertRule.setName(DalLockingTest.class.getName() + " - Testing Alert 
Rule");
+      OBDal.getInstance().save(newAlertRule);
+
+      testingRuleId = newAlertRule.getId();
+      OBDal.getInstance().commitAndClose();
+    } finally {
+      OBContext.restorePreviousMode();
+    }
+  }
+
+  @Test
+  public void lockedObjectShouldBeANewInstance() {
+    AlertRule originalInstance = getTestingAlertRule();
+    AlertRule lockedAlert = 
OBDal.getInstance().getObjectLockForNoKeyUpdate(originalInstance);
+
+    assertThat(lockedAlert, not(sameInstance(originalInstance)));
+  }
+
+  @Test
+  public void originalObjectShouldBeDetachedFromSession() {
+    AlertRule originalInstance = getTestingAlertRule();
+    OBDal.getInstance().getObjectLockForNoKeyUpdate(originalInstance);
+
+    // originalInstance is now detached from DAL session. If any of its 
proxies is tried to be
+    // initialized, it should fail.
+    thrown.expect(LazyInitializationException.class);
+    originalInstance.getADAlertRecipientList().size();
+  }
+
+  @Test
+  public void objectShouldBeLockedInDB() throws InterruptedException, 
ExecutionException {
+    List<Callable<Void>> threads = Arrays.asList(
+        //
+        getDalCallable(this::acquireLock, "T1", 0, 200),
+        getDalCallable(this::acquireLock, "T2", 100, 0));
+
+    executeAndGetResults(threads);
+    assertThat(
+        "Execution Order: Thread that acquired lock (T1) should finish before 
the one trying to acquire it afterwards (T2).",
+        executionOrder, is(Arrays.asList("T1", "T2")));
+  }
+
+  @Test
+  public void lockedObjectShouldAllowChildrenCreation() throws 
InterruptedException,
+      ExecutionException {
+    List<Callable<Void>> threads = Arrays.asList( //
+        getDalCallable(this::acquireLock, "T1", 0, 200), //
+        getDalCallable(() -> {
+          AlertRecipient recipient = 
OBProvider.getInstance().get(AlertRecipient.class);
+          recipient.setRole(OBContext.getOBContext().getRole());
+          recipient.setAlertRule(getTestingAlertRule());
+          OBDal.getInstance().save(recipient);
+        }, "T2", 100, 0));
+
+    executeAndGetResults(threads);
+    assertThat(
+        "Execution Order: Even T1 acquired a lock on parent, T2 can insert 
children without waiting for it",
+        executionOrder, is(Arrays.asList("T2", "T1")));
+  }
+
+  @Test
+  public void lockedInstanceGetsRefreshedFromDB() throws InterruptedException, 
ExecutionException {
+    StringBuilder originalName = new StringBuilder();
+    StringBuilder lockedName = new StringBuilder();
+    List<Callable<Void>> threads = Arrays.asList( //
+        getDalCallable(() -> {
+          AlertRule ar = getTestingAlertRule();
+          originalName.append(ar.getName());
+
+          try {
+            TimeUnit.MILLISECONDS.sleep(200);
+          } catch (InterruptedException e) {
+          }
+
+          
lockedName.append(OBDal.getInstance().getObjectLockForNoKeyUpdate(ar).getName());
+        }, "T1", 0, 0), //
+        getDalCallable(() -> {
+          getTestingAlertRule().setName("Modified");
+        }, "T2", 100, 0));
+
+    executeAndGetResults(threads);
+
+    assertThat("Execution Order", executionOrder, is(Arrays.asList("T2", 
"T1")));
+    assertThat("Name changed", lockedName.toString(), 
not(originalName.toString()));
+    assertThat("Name changed", lockedName.toString(), is("Modified"));
+  }
+
+  /** Note once HHH-13135 is fixed and Dal lock adapted to make use of it, 
this test should fail. */
+  @Test
+  public void dalLockIsNotAHibernateLock() {
+    AlertRule lockedRule = acquireLock();
+    LockMode lm = 
OBDal.getInstance().getSession().getCurrentLockMode(lockedRule);
+    assertTrue("Hibernate lock mode doesn't match DAL's one", 
lm.lessThan(LockMode.WRITE));
+  }
+
+  @AfterClass
+  public static void cleanUpTestEnvironment() {
+    OBContext.setAdminMode();
+    try {
+      OBDal.getInstance().remove(getTestingAlertRule());
+      OBDal.getInstance().commitAndClose();
+    } finally {
+      OBContext.restorePreviousMode();
+    }
+  }
+
+  private Callable<Void> getDalCallable(Runnable r, String name, long 
waitBefore, long waitAfter) {
+    return () -> {
+      boolean errorOccurred = false;
+      try {
+        OBContext.setAdminMode();
+        TimeUnit.MILLISECONDS.sleep(waitBefore);
+        r.run();
+        TimeUnit.MILLISECONDS.sleep(waitAfter);
+        return null;
+      } catch (Exception e) {
+        log.error("Error occurred executing dal action", e);
+        OBDal.getInstance().rollbackAndClose();
+        errorOccurred = true;
+        throw new OBException(e);
+      } finally {
+        if (!errorOccurred) {
+          OBDal.getInstance().commitAndClose();
+        }
+        OBContext.restorePreviousMode();
+        log.info("Completed thread {}", name);
+        executionOrder.add(name);
+      }
+    };
+  }
+
+  private <T extends Object> void executeAndGetResults(List<Callable<T>> 
threads)
+      throws InterruptedException, ExecutionException {
+    executionOrder = new ArrayList<>();
+    ExecutorService executorService = Executors.newFixedThreadPool(2);
+    List<Future<T>> results = executorService.invokeAll(threads);
+    for (Future<T> result : results) {
+      // get to throw exception if it failed
+      result.get();
+    }
+  }
+
+  private AlertRule acquireLock() {
+    return OBDal.getInstance().getObjectLockForNoKeyUpdate(
+        OBDal.getInstance().getProxy(AlertRule.class, testingRuleId));
+  }
+
+  private static AlertRule getTestingAlertRule() {
+    return OBDal.getInstance().get(AlertRule.class, testingRuleId);
+  }
+}
diff -r 4c4039a9c917 -r 7e4c318ad6a3 src/org/openbravo/dal/service/OBDal.java
--- a/src/org/openbravo/dal/service/OBDal.java  Tue Dec 11 12:26:44 2018 +0000
+++ b/src/org/openbravo/dal/service/OBDal.java  Tue Dec 11 15:37:30 2018 +0100
@@ -27,6 +27,8 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.persistence.LockModeType;
+
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.hibernate.ObjectNotFoundException;
@@ -730,16 +732,24 @@
   }
 
   /**
-   * Retrieves an object from the database getting a lock "for no key update" 
for the indicated
-   * object. Note that the object entering in the method is evicted and a new 
object is created.
-   * Before calling this method, is a must check if changes have been made to 
the object previously.
-   * In this case a flush must be done.
+   * Creates a WRITE lock in database for the DAL persistence instance {@code 
object} parameter and
+   * returns a new instance representing the same database object.
+   * <p>
+   * Note the original instance that is passed as parameter is evicted from 
Hibernate's 1st level.
+   * Therefore, any state not persisted before invoking this method will be 
ignored, after invoking
+   * this method the parameter instance shouldn't be used anymore using 
instead the returned one.
+   * <p>
+   * Whereas this is similar to JPA's {@link LockModeType#PESSIMISTIC_WRITE}, 
it decreases lock
+   * level in PostgreSQL implemented by Hibernate from {@code FOR UPDATE} to
+   * {@code FOR NO KEY UPDATE} allowing insertions of children records while a 
lock on its parent is
+   * acquired by a different transaction. This is a workaround until Hibernate 
issue HHH-13135 is
+   * fixed. Unlike locks acquired by Hibernate, the ones created by this 
method are only present in
+   * Database and cannot be detected by Hibernate (eg. {@link 
Session#getCurrentLockMode(Object)}.
    * 
    * @param object
-   *          the type to create the query for
-   * @return the new object getting a lock "for no key update"
+   *          DAL instance to acquire a database lock for.
+   * @return A new DAL instance that represents the same database object than 
the parameter.
    */
-  @SuppressWarnings("unchecked")
   public <T extends BaseOBObject> T getObjectLockForNoKeyUpdate(T object) {
     Entity entity = object.getEntity();
 
@@ -756,7 +766,10 @@


_______________________________________________
Openbravo-commits mailing list
Openbravo-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openbravo-commits

Reply via email to