diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml index a2c9053c89e5588da519e4a3faccc193d6f278e1..3510238f02dfcf6c04dd733153a357be22d5b219 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml @@ -48,7 +48,6 @@ <mapping-file>org/olat/course/nodes/projectbroker/datamodel/Project.hbm.xml</mapping-file> <mapping-file>org/olat/course/nodes/projectbroker/datamodel/ProjectBroker.hbm.xml</mapping-file> <mapping-file>org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml</mapping-file> - <mapping-file>org/olat/course/assessment/model/UserCourseInfosImpl.hbm.xml</mapping-file> <mapping-file>org/olat/modules/fo/ForumImpl.hbm.xml</mapping-file> <mapping-file>org/olat/modules/fo/MessageImpl.hbm.xml</mapping-file> <mapping-file>org/olat/modules/fo/ReadMessage.hbm.xml</mapping-file> @@ -92,6 +91,7 @@ <class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class> <class>org.olat.core.commons.services.taskexecutor.model.PersistentTask</class> <class>org.olat.core.commons.services.taskexecutor.model.PersistentTaskModifier</class> + <class>org.olat.course.assessment.model.UserCourseInfosImpl</class> <class>org.olat.group.model.BusinessGroupParticipantViewImpl</class> <class>org.olat.group.model.BusinessGroupOwnerViewImpl</class> <class>org.olat.group.model.BusinessGroupLazyImpl</class> diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java index d7067095d9275cdc53aba59940dfc19aa7ef4251..5b51b3fc5fd6200c88bb9c551738c3f17b50ad99 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java @@ -39,7 +39,7 @@ public interface UserCourseInformationsManager { public List<UserCourseInformations> getUserCourseInformations(Identity identity, List<OLATResource> resources); - public void updateUserCourseInformations(Long courseResId, Identity identity); + public void updateUserCourseInformations(Long courseResId, Identity identity, boolean strict); public Date getInitialLaunchDate(Long courseResourceId, Identity identity); diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java index d1a3dc5ff3e4f57f2768fbb45887f2af6f51fed6..4da9e894e903d02fbfc47d4aafc51226b020033d 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java @@ -65,8 +65,9 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U try { StringBuilder sb = new StringBuilder(); sb.append("select infos from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") - .append(" inner join infos.resource as resource") - .append(" where infos.identity.key=:identityKey and resource.resId=:resId and resource.resName='CourseModule'"); + .append(" inner join fetch infos.resource as resource") + .append(" inner join infos.identity as identity") + .append(" where identity.key=:identityKey and resource.resId=:resId and resource.resName='CourseModule'"); List<UserCourseInfosImpl> infoList = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), UserCourseInfosImpl.class) @@ -93,8 +94,8 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U try { StringBuilder sb = new StringBuilder(); sb.append("select infos from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") - .append(" inner join fetch infos.resource as resource") - .append(" inner join fetch infos.identity as identity") + .append(" inner join fetch infos.resource as resource") + .append(" inner join infos.identity as identity") .append(" where identity.key=:identityKey and resource.key in (:resKeys)"); List<Long> resourceKeys = PersistenceHelper.toKeys(resources); @@ -117,35 +118,94 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U * @return */ @Override - public void updateUserCourseInformations(final Long courseResourceableId, final Identity identity) { - OLATResourceable lockRes = OresHelper.createOLATResourceableInstance("CourseLaunchDate::Identity", identity.getKey()); - CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(lockRes, new SyncerExecutor(){ - @Override - public void execute() { - try { - UserCourseInfosImpl infos = getUserCourseInformations(courseResourceableId, identity); - if(infos == null) { - OLATResource courseResource = resourceManager.findResourceable(courseResourceableId, "CourseModule"); - infos = new UserCourseInfosImpl(); - infos.setIdentity(identity); - infos.setInitialLaunch(new Date()); - infos.setLastModified(new Date()); - infos.setRecentLaunch(new Date()); - infos.setVisit(1); - infos.setResource(courseResource); - dbInstance.getCurrentEntityManager().persist(infos); - } else if(needUpdate(infos)) { - dbInstance.getCurrentEntityManager().refresh(infos); - infos.setVisit(infos.getVisit() + 1); - infos.setRecentLaunch(new Date()); - infos.setLastModified(new Date()); - infos = dbInstance.getCurrentEntityManager().merge(infos); + public void updateUserCourseInformations(final Long courseResourceableId, final Identity identity, final boolean strict) { + UltraLightInfos ulInfos = getUserCourseInformationsKey(courseResourceableId, identity); + if(ulInfos == null) { + OLATResourceable lockRes = OresHelper.createOLATResourceableInstance("CourseLaunchDate::Identity", identity.getKey()); + CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(lockRes, new SyncerExecutor(){ + @Override + public void execute() { + try { + UltraLightInfos ulInfos = getUserCourseInformationsKey(courseResourceableId, identity); + if(ulInfos == null) { + OLATResource courseResource = resourceManager.findResourceable(courseResourceableId, "CourseModule"); + UserCourseInfosImpl infos = new UserCourseInfosImpl(); + infos.setIdentity(identity); + infos.setCreationDate(new Date()); + infos.setInitialLaunch(new Date()); + infos.setLastModified(new Date()); + infos.setRecentLaunch(new Date()); + infos.setVisit(1); + infos.setResource(courseResource); + dbInstance.getCurrentEntityManager().persist(infos); + } else if(strict || needUpdate(ulInfos)) { + UserCourseInfosImpl infos = loadById(ulInfos.getKey()); + if(infos != null) { + infos.setVisit(infos.getVisit() + 1); + infos.setRecentLaunch(new Date()); + infos.setLastModified(new Date()); + infos = dbInstance.getCurrentEntityManager().merge(infos); + } + } + } catch (Exception e) { + logError("Cannot update course informations for: " + identity + " from " + identity, e); } - } catch (Exception e) { - logError("Cannot update course informations for: " + identity + " from " + identity, e); } + }); + } else if(strict || needUpdate(ulInfos)) { + UserCourseInfosImpl infos = loadById(ulInfos.getKey()); + if(infos != null) { + infos.setVisit(infos.getVisit() + 1); + infos.setRecentLaunch(new Date()); + infos.setLastModified(new Date()); + infos = dbInstance.getCurrentEntityManager().merge(infos); } - }); + } + } + + private UserCourseInfosImpl loadById(Long id) { + try { + String sb = "select infos from usercourseinfos as infos where infos.key=:key"; + + TypedQuery<UserCourseInfosImpl> query = dbInstance.getCurrentEntityManager() + .createQuery(sb, UserCourseInfosImpl.class) + .setParameter("key", id); + + List<UserCourseInfosImpl> infoList = query.getResultList(); + if(infoList.isEmpty()) { + return null; + } + return infoList.get(0); + } catch (Exception e) { + logError("Cannot retrieve course informations for: " + id, e); + return null; + } + } + + private UltraLightInfos getUserCourseInformationsKey(Long courseResourceId, Identity identity) { + try { + StringBuilder sb = new StringBuilder(); + sb.append("select infos.key, infos.lastModified from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") + .append(" inner join infos.resource as resource") + .append(" inner join infos.identity as identity") + .append(" where identity.key=:identityKey and resource.resId=:resId and resource.resName='CourseModule'"); + + List<Object[]> infoList = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class) + .setParameter("identityKey", identity.getKey()) + .setParameter("resId", courseResourceId) + .getResultList(); + + if(infoList.isEmpty()) { + return null; + } + Object[] infos = infoList.get(0); + + return new UltraLightInfos((Long)infos[0], (Date)infos[1]); + } catch (Exception e) { + logError("Cannot retrieve course informations for: " + identity + " from " + identity, e); + return null; + } } /** @@ -155,10 +215,10 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U * opens a course several times. * @return */ - private final boolean needUpdate(UserCourseInfosImpl infos) { + private final boolean needUpdate(UltraLightInfos infos) { Date lastModified = infos.getLastModified(); if(System.currentTimeMillis() - lastModified.getTime() < 60000) { - return false; + return true; } return true; } @@ -173,7 +233,8 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U StringBuilder sb = new StringBuilder(); sb.append("select infos.initialLaunch from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") .append(" inner join infos.resource as resource") - .append(" where infos.identity.key=:identityKey and resource.resId=:resId and resource.resName='CourseModule'"); + .append(" inner join infos.identity as identity") + .append(" where identity.key=:identityKey and resource.resId=:resId and resource.resName='CourseModule'"); List<Date> infoList = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Date.class) @@ -256,4 +317,21 @@ public class UserCourseInformationsManagerImpl extends BasicManager implements U return -1; } } + + private static class UltraLightInfos { + private final Long key; + private final Date lastModified; + + public UltraLightInfos(Long key, Date lastModified) { + this.key = key; + this.lastModified = lastModified; + } + + public Long getKey() { + return key; + } + public Date getLastModified() { + return lastModified; + } + } } \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.hbm.xml b/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.hbm.xml deleted file mode 100644 index b34c9dfc065a0d1949f2a62f132213348432170d..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.hbm.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0"?> -<!DOCTYPE hibernate-mapping PUBLIC - "-//Hibernate/Hibernate Mapping DTD//EN" - "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> -<hibernate-mapping default-lazy="false"> - - <class name="org.olat.course.assessment.model.UserCourseInfosImpl" table="o_as_user_course_infos"> - <cache usage="transactional" /> - - <id name="key" type="long" column="id" unsaved-value="null"> - <generator class="hilo"/> - </id> - - <version name="version" access="field" column="version" type="int"/> - <property name="creationDate" column="creationdate" type="timestamp" /> - <property name="lastModified" column="lastmodified" type="timestamp" /> - - - <property name="initialLaunch" column="initiallaunchdate" type="timestamp" /> - <property name="recentLaunch" column="recentlaunchdate" type="timestamp" /> - - <property name="visit" column="visit" type="int" /> - <property name="timeSpend" column="timespend" type="long" /> - - <many-to-one name="resource" - column="fk_resource_id" - foreign-key="none" - class="org.olat.resource.OLATResourceImpl" - outer-join="true" - unique="false" - not-found="ignore" - cascade="none"/> - - <many-to-one name="identity" - column="fk_identity" - foreign-key="cx_eff_statement_to_identity" - class="org.olat.basesecurity.IdentityImpl" - outer-join="true" - unique="false" - cascade="none"/> - - </class> -</hibernate-mapping> \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.java b/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.java index c1b870c652cfdf164f01949990f8952328fb0cc6..e7692db2badb0417d13e792d41f3d61b9929bd29 100644 --- a/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.java +++ b/src/main/java/org/olat/course/assessment/model/UserCourseInfosImpl.java @@ -1,5 +1,5 @@ /** - * <a href="http://www.openolat.org"> +$ * <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> @@ -21,25 +21,84 @@ package org.olat.course.assessment.model; import java.util.Date; -import org.olat.core.commons.persistence.PersistentObject; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.GenericGenerator; +import org.olat.basesecurity.IdentityImpl; import org.olat.core.id.Identity; import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; import org.olat.course.assessment.UserCourseInformations; import org.olat.resource.OLATResource; +import org.olat.resource.OLATResourceImpl; -public class UserCourseInfosImpl extends PersistentObject implements UserCourseInformations, ModifiedInfo { +@Entity(name="usercourseinfos") +@Table(name="o_as_user_course_infos") +public class UserCourseInfosImpl implements UserCourseInformations, Persistable, ModifiedInfo { private static final long serialVersionUID = -6933599547069673655L; + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "hilo") + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + //de facto removing the optimistic locking + @Column(name="version", nullable=false, insertable=true, updatable=false) + private Integer version = 0; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) private Date lastModified; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="initiallaunchdate", nullable=false, insertable=true, updatable=true) private Date initialLaunch; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="recentlaunchdate", nullable=false, insertable=true, updatable=true) private Date recentLaunch; + + @Column(name="visit", nullable=false, insertable=true, updatable=true) private int visit; + @Column(name="timespend", nullable=false, insertable=true, updatable=true) private long timeSpend; + @ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_identity", nullable=false, updatable=false) private Identity identity; + @ManyToOne(targetEntity=OLATResourceImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_resource_id", nullable=false, updatable=false) private OLATResource resource; + @Override + public Long getKey() { + return key; + } + + public void setKey(Long key) { + this.key = key; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + @Override public Date getLastModified() { return lastModified; @@ -107,6 +166,11 @@ public class UserCourseInfosImpl extends PersistentObject implements UserCourseI public int hashCode() { return getKey() == null ? 9271 : getKey().hashCode(); } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } @Override public boolean equals(Object obj) { diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java index 01ff99294d9ace375a12e49636290a399e99dd02..aaed4a201919029d528d82359c0d6a75d8a1737e 100644 --- a/src/main/java/org/olat/course/run/RunMainController.java +++ b/src/main/java/org/olat/course/run/RunMainController.java @@ -344,8 +344,8 @@ public class RunMainController extends MainLayoutBasicController implements Gene } private void setLaunchDates(final Identity identity) { - UserCourseInformationsManager efficiencyStatementManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); - efficiencyStatementManager.updateUserCourseInformations(uce.getCourseEnvironment().getCourseResourceableId(), getIdentity()); + UserCourseInformationsManager userCourseInfoMgr = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); + userCourseInfoMgr.updateUserCourseInformations(uce.getCourseEnvironment().getCourseResourceableId(), getIdentity(), false); } /** diff --git a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java index 9d0316529bbbf556f9c79c642fa4293a0e1a36ab..106d0f6dbc66b9c069c2b384d5b0c010480cb316 100644 --- a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java +++ b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java @@ -20,15 +20,20 @@ package org.olat.course.assessment.manager; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.course.ICourse; import org.olat.course.assessment.UserCourseInformations; import org.olat.restapi.repository.course.CoursesWebService; @@ -43,6 +48,8 @@ import org.springframework.beans.factory.annotation.Autowired; * */ public class UserCourseInformationsManagerTest extends OlatTestCase { + + private static final OLog log = Tracing.createLoggerFor(UserCourseInformationsManagerTest.class); @Autowired private DB dbInstance; @@ -50,12 +57,12 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { private UserCourseInformationsManager userCourseInformationsManager; @Test - public void createUpdateCourseInfos() { + public void createUpdateCourseInfos_create() { Identity user = JunitTestHelper.createAndPersistIdentityAsUser("user-launch-1-" + UUID.randomUUID().toString()); ICourse course = CoursesWebService.createEmptyCourse(user, "course-launch-dates", "course long name", null); dbInstance.commitAndCloseSession(); - userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user, true); dbInstance.commitAndCloseSession(); UserCourseInformations infos = userCourseInformationsManager.getUserCourseInformations(course.getResourceableId(), user); @@ -71,13 +78,30 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { Assert.assertEquals(course.getResourceableTypeName(), infos.getResource().getResourceableTypeName()); } + @Test + public void createUpdateCourseInfos_updateToo() { + Identity user = JunitTestHelper.createAndPersistIdentityAsUser("user-launch-1-" + UUID.randomUUID().toString()); + ICourse course = CoursesWebService.createEmptyCourse(user, "course-launch-dates", "course long name", null); + dbInstance.commitAndCloseSession(); + + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user, true); + dbInstance.commitAndCloseSession(); + + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user, true); + dbInstance.commitAndCloseSession(); + + UserCourseInformations infos = userCourseInformationsManager.getUserCourseInformations(course.getResourceableId(), user); + Assert.assertNotNull(infos); + Assert.assertNotNull(infos.getIdentity()); + } + @Test public void getInitialLaunchDate() { Identity user = JunitTestHelper.createAndPersistIdentityAsUser("user-launch-2-" + UUID.randomUUID().toString()); ICourse course = CoursesWebService.createEmptyCourse(user, "course-launch-dates", "course long name", null); dbInstance.commitAndCloseSession(); - userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user, true); dbInstance.commitAndCloseSession(); Date launchDate = userCourseInformationsManager.getInitialLaunchDate(course.getResourceableId(), user); @@ -91,8 +115,8 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { ICourse course = CoursesWebService.createEmptyCourse(user1, "course-launch-dates", "course long name", null); dbInstance.commitAndCloseSession(); - userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1); - userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1, true); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2, true); dbInstance.commitAndCloseSession(); List<Identity> users = new ArrayList<Identity>(); @@ -107,4 +131,105 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { Assert.assertTrue(launchDates.containsKey(user2.getKey())); Assert.assertNotNull(launchDates.get(user2.getKey())); } + + + /** + * This test is to analyze a red screen + */ + @Test + public void updateInitialLaunchDates_loop() { + Identity user = JunitTestHelper.createAndPersistIdentityAsUser("user-launch-5-" + UUID.randomUUID().toString()); + ICourse course = CoursesWebService.createEmptyCourse(user, "course-launch-dates", "course long name", null); + dbInstance.commitAndCloseSession(); + + for(int i=0; i<10; i++) { + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user, true); + } + dbInstance.commitAndCloseSession(); + + List<Identity> users = Collections.singletonList(user); + Map<Long,Date> launchDates = userCourseInformationsManager.getInitialLaunchDates(course.getResourceableId(), users); + Assert.assertNotNull(launchDates); + Assert.assertEquals(1, launchDates.size()); + Assert.assertTrue(launchDates.containsKey(user.getKey())); + Assert.assertNotNull(launchDates.get(user.getKey())); + } + + /** + * This test is to analyze a red screen + */ + @Test + public void updateInitialLaunchDates_concurrent() { + Identity user = JunitTestHelper.createAndPersistIdentityAsUser("user-launch-concurrent-6-" + UUID.randomUUID().toString()); + ICourse course = CoursesWebService.createEmptyCourse(user, "course-concurrent-launch-dates", "course long name", null); + dbInstance.commitAndCloseSession(); + + final int numThreads = 20; + + CountDownLatch latch = new CountDownLatch(numThreads); + UpdateThread[] threads = new UpdateThread[numThreads]; + for(int i=0; i<threads.length;i++) { + threads[i] = new UpdateThread(user, course.getResourceableId(), userCourseInformationsManager, latch, dbInstance); + } + + for(int i=0; i<threads.length;i++) { + threads[i].start(); + } + + try { + latch.await(120, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("", e); + } + + int countErrors = 0; + for(int i=0; i<threads.length;i++) { + countErrors += threads[i].getErrors(); + } + + Assert.assertEquals(0, countErrors); + } + + private static class UpdateThread extends Thread { + + private final DB db; + private final CountDownLatch latch; + private final UserCourseInformationsManager uciManager; + + private final Long courseResourceableId; + private final Identity user; + + private int errors = 0; + + public UpdateThread(Identity user, Long courseResourceableId, + UserCourseInformationsManager uciManager, CountDownLatch latch, DB db) { + this.user = user; + this.courseResourceableId = courseResourceableId; + this.uciManager = uciManager; + this.latch = latch; + this.db = db; + } + + public int getErrors() { + return errors; + } + + @Override + public void run() { + try { + Thread.sleep(10); + for(int i=0; i<25;i++) { + uciManager.updateUserCourseInformations(courseResourceableId, user, true); + uciManager.getUserCourseInformations(courseResourceableId, user); + uciManager.updateUserCourseInformations(courseResourceableId, user, true); + db.commitAndCloseSession(); + } + } catch (Exception e) { + log.error("", e); + errors++; + } finally { + latch.countDown(); + } + } + } }