From f32c2536b74bdbbf1b4fe07959d938621a634343 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 3 Sep 2013 11:15:54 +0200
Subject: [PATCH] OO-753: remove the optimistic locking from
 SecurityGroupMemebership and make almost all field immutable

---
 .../basesecurity/BaseSecurityManager.java     |   6 +-
 .../SecurityGroupMembershipImpl.hbm.xml       |  14 +-
 .../SecurityGroupMembershipImpl.java          |  12 +-
 .../basesecurity/BaseSecurityManagerTest.java |  45 +++++
 .../test/BusinessGroupConcurrentTest.java     | 167 ++++++++++++++++++
 .../java/org/olat/test/AllTestsJunit4.java    |   1 +
 6 files changed, 234 insertions(+), 11 deletions(-)
 create mode 100644 src/test/java/org/olat/group/test/BusinessGroupConcurrentTest.java

diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
index 5846f911334..16f0d0c8894 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
@@ -530,15 +530,17 @@ public class BaseSecurityManager extends BasicManager implements BaseSecurity {
 	}
 
 	/**
+	 * 
+	 * 
 	 * @see org.olat.basesecurity.Manager#addIdentityToSecurityGroup(org.olat.core.id.Identity, org.olat.basesecurity.SecurityGroup)
 	 */
+	@Override
 	public void addIdentityToSecurityGroup(Identity identity, SecurityGroup secGroup) {
 		SecurityGroupMembershipImpl sgmsi = new SecurityGroupMembershipImpl();
 		sgmsi.setIdentity(identity);
 		sgmsi.setSecurityGroup(secGroup);
 		sgmsi.setLastModified(new Date());
-		DBFactory.getInstance().saveObject(sgmsi);
-		//TODO: tracing
+		dbInstance.getCurrentEntityManager().persist(sgmsi);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.hbm.xml b/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.hbm.xml
index e3099279059..3953895507f 100644
--- a/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.hbm.xml
+++ b/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.hbm.xml
@@ -8,16 +8,16 @@
       <generator class="hilo"/>
     </id>
     
-    <version name="version" access="field" column="version" type="int"/>
-    <property  name="lastModified" column="lastmodified" type="timestamp" />
-	<property  name="creationDate" column="creationdate" type="timestamp" />   
+    <property name="version" access="field" column="version" insert="true" update="false" type="int"/>
+    <property name="lastModified" column="lastmodified" type="timestamp" />
+		<property name="creationDate" column="creationdate" insert="true" update="false" type="timestamp" />   
          
-    <many-to-one name="securityGroup" class="org.olat.basesecurity.SecurityGroupImpl" fetch="join" cascade="none">  
-		<column name="secgroup_id" not-null="true"	unique-key="groupmembership_unique" />    	
+    <many-to-one name="securityGroup" class="org.olat.basesecurity.SecurityGroupImpl" fetch="join" insert="true" update="false" cascade="none">  
+			<column name="secgroup_id" not-null="true"	unique-key="groupmembership_unique" />    	
     </many-to-one>  	
    
-	<many-to-one name="identity" class="org.olat.basesecurity.IdentityImpl" fetch="join" cascade="none">  
-		<column name="identity_id" not-null="true"	unique-key="groupmembership_unique" />    	
+		<many-to-one name="identity" class="org.olat.basesecurity.IdentityImpl" fetch="join" insert="true" update="false" cascade="none">  
+			<column name="identity_id" not-null="true"	unique-key="groupmembership_unique" />    	
     </many-to-one>  	
         	
   </class>
diff --git a/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.java b/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.java
index 42540917b57..1f58ae5aa7a 100644
--- a/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.java
+++ b/src/main/java/org/olat/basesecurity/SecurityGroupMembershipImpl.java
@@ -33,10 +33,16 @@ import org.olat.core.id.ModifiedInfo;
 
 /**
  * Description: <br>
+ * Relation between the security group and identity. The object is almost immutable
+ * on the hibernate mapping.
+ * 
  * 
  * @author Felix Jost
  */
 public class SecurityGroupMembershipImpl extends PersistentObject implements ModifiedInfo {
+
+	private static final long serialVersionUID = 2466302280763907357L;
+	
 	private Identity identity;
 	private SecurityGroup securityGroup;
 	private Date lastModified;
@@ -63,7 +69,8 @@ public class SecurityGroupMembershipImpl extends PersistentObject implements Mod
 	}
 
 	/**
-	 * Sets the identity.
+	 * Sets the identity. The identity cannot be changed and updated.
+	 * The identity is only inserted to the database but never updated
 	 * 
 	 * @param identity The identity to set
 	 */
@@ -72,7 +79,8 @@ public class SecurityGroupMembershipImpl extends PersistentObject implements Mod
 	}
 
 	/**
-	 * Sets the securityGroup.
+	 * Sets the securityGroup. The security group cannot be changed and
+	 * updated. It is only inserted to the dabatase but never updated.
 	 * 
 	 * @param securityGroup The securityGroup to set
 	 */
diff --git a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
index 3b57ecd6ab7..f20dc906bb9 100644
--- a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
+++ b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
@@ -24,7 +24,9 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -845,4 +847,47 @@ public class BaseSecurityManagerTest extends OlatTestCase {
 		String credential = securityManager.findCredentials(id, BaseSecurityModule.getDefaultAuthProviderIdentifier());
 		Assert.assertNotNull(credential);	
 	}
+	
+	/**
+	 * Dummy test to make sure all works as wanted
+	 */
+	@Test
+	public void createSecurityGroupMembership() {
+		//create a user with the default provider
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsUser("update-membership-" + UUID.randomUUID().toString());
+		SecurityGroup secGroup = securityManager.createAndPersistSecurityGroup();
+		securityManager.addIdentityToSecurityGroup(identity, secGroup);
+		dbInstance.commitAndCloseSession();
+
+		boolean member = securityManager.isIdentityInSecurityGroup(identity, secGroup);
+		Assert.assertTrue(member);
+	}
+	
+	/**
+	 * We remove the optimistic locking from SecurityGroupMembershipImpl mapping
+	 */
+	@Test
+	public void createAndUpdateSecurityGroupMembership_lastCommitWin() {
+		//create a user with the default provider
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsUser("update-membership-" + UUID.randomUUID().toString());
+		SecurityGroup secGroup = securityManager.createAndPersistSecurityGroup();
+		
+		SecurityGroupMembershipImpl sgmsi = new SecurityGroupMembershipImpl();
+		sgmsi.setIdentity(identity);
+		sgmsi.setSecurityGroup(secGroup);
+		sgmsi.setLastModified(new Date());
+		dbInstance.getCurrentEntityManager().persist(sgmsi);
+		dbInstance.commitAndCloseSession();
+
+		Calendar cal = Calendar.getInstance();
+		cal.add(Calendar.DATE, -1);
+		sgmsi.setLastModified(cal.getTime());
+		dbInstance.getCurrentEntityManager().merge(sgmsi);
+		dbInstance.commitAndCloseSession();
+	
+		cal.add(Calendar.DATE, -1);
+		sgmsi.setLastModified(cal.getTime());
+		dbInstance.getCurrentEntityManager().merge(sgmsi);
+		dbInstance.commitAndCloseSession();	
+	}
 }
diff --git a/src/test/java/org/olat/group/test/BusinessGroupConcurrentTest.java b/src/test/java/org/olat/group/test/BusinessGroupConcurrentTest.java
new file mode 100644
index 00000000000..19681067237
--- /dev/null
+++ b/src/test/java/org/olat/group/test/BusinessGroupConcurrentTest.java
@@ -0,0 +1,167 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.group.test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
+import org.olat.group.manager.BusinessGroupDAO;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 28.08.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BusinessGroupConcurrentTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private BusinessGroupDAO businessGroupDao;
+	
+
+	
+	@Test
+	public void concurrentSetLastUsageFor_multipleUser() {
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "gdao", "gdao-desc", -1, -1, false, false, false, false, false);
+		dbInstance.commit();
+
+		int numOfThreads = 25;
+		final CountDownLatch doneSignal = new CountDownLatch(numOfThreads);
+		
+		SetLastUsageThread[] threads = new SetLastUsageThread[numOfThreads];
+		for(int i=numOfThreads; i-->0; ) {
+			Identity id = JunitTestHelper.createAndPersistIdentityAsUser("group-concurent-" + i + "-" + UUID.randomUUID().toString());
+			securityManager.addIdentityToSecurityGroup(id, group.getPartipiciantGroup());
+			threads[i] = new SetLastUsageThread(group.getKey(), id, doneSignal);
+		}
+		dbInstance.commitAndCloseSession();;
+		
+		for(int i=numOfThreads; i-->0; ) {
+			threads[i].start();
+		}
+
+		try {
+			boolean interrupt = doneSignal.await(240, TimeUnit.SECONDS);
+			assertTrue("Test takes too long (more than 10s)", interrupt);
+		} catch (InterruptedException e) {
+			fail("" + e.getMessage());
+		}
+		
+		int errorCount = 0;
+		for(int i=numOfThreads; i-->0; ) {
+			errorCount += threads[i].getErrorCount();
+		}
+		Assert.assertEquals(0, errorCount);
+	}
+	
+	@Test
+	public void concurrentSetLastUsageFor_singleUser() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsUser("group-cc-single-" + UUID.randomUUID().toString());
+		BusinessGroup group = businessGroupDao.createAndPersist(id, "gdao", "gdao-desc", -1, -1, false, false, false, false, false);
+		dbInstance.commitAndCloseSession();
+
+		int numOfThreads = 25;
+		final CountDownLatch doneSignal = new CountDownLatch(numOfThreads);
+		
+		SetLastUsageThread[] threads = new SetLastUsageThread[numOfThreads];
+		for(int i=numOfThreads; i-->0; ) {
+			threads[i] = new SetLastUsageThread(group.getKey(), id, doneSignal);
+		}
+		
+		for(int i=numOfThreads; i-->0; ) {
+			threads[i].start();
+		}
+
+		try {
+			boolean interrupt = doneSignal.await(240, TimeUnit.SECONDS);
+			assertTrue("Test takes too long (more than 10s)", interrupt);
+		} catch (InterruptedException e) {
+			fail("" + e.getMessage());
+		}
+		
+		int errorCount = 0;
+		for(int i=numOfThreads; i-->0; ) {
+			errorCount += threads[i].getErrorCount();
+		}
+		Assert.assertEquals(0, errorCount);
+	}
+	
+	
+	private class SetLastUsageThread extends Thread {
+		
+		private AtomicInteger errorCount = new AtomicInteger();
+		
+		private final Long key;
+		private final Identity identity;
+		private final CountDownLatch doneSignal;
+		
+		public SetLastUsageThread(Long key, Identity identity, CountDownLatch doneSignal) {
+			this.key = key;
+			this.identity = identity;
+			this.doneSignal = doneSignal;
+		}
+		
+		public int getErrorCount() {
+			return errorCount.get();
+		}
+	
+		@Override
+		public void run() {
+			try {
+				Thread.sleep(10);
+			} catch (InterruptedException e) {
+				e.printStackTrace();
+			}
+			BusinessGroupService service = CoreSpringFactory.getImpl(BusinessGroupService.class);
+			try {
+				BusinessGroup group = service.loadBusinessGroup(key);
+				for(int i=50; i-->0; ) {
+					group = service.setLastUsageFor(identity, group);
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+				errorCount.incrementAndGet();
+			} finally {
+				doneSignal.countDown();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index a20ceb906b2..308bb1b94d3 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -83,6 +83,7 @@ import org.junit.runners.Suite;
 	org.olat.group.test.BusinessGroupServiceTest.class,//ok
 	org.olat.group.test.BusinessGroupDAOTest.class,//ok
 	org.olat.group.test.BusinessGroupRelationDAOTest.class,//ok
+	org.olat.group.test.BusinessGroupConcurrentTest.class,//ok
 	org.olat.group.test.ContactDAOTest.class,//ok
 	org.olat.resource.lock.pessimistic.PLockTest.class,//ok
 	org.olat.resource.references.ReferenceManagerTest.class,//ok
-- 
GitLab