/** * <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.repository.manager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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.commons.services.commentAndRating.manager.UserCommentsDAO; import org.olat.core.commons.services.commentAndRating.manager.UserRatingsDAO; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.model.RepositoryEntryStatistics; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 10.06.2013<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class RepositoryEntryStatisticsDAOTest extends OlatTestCase { private static final OLog log = Tracing.createLoggerFor(RepositoryEntryStatisticsDAOTest.class); @Autowired private DB dbInstance; @Autowired private UserRatingsDAO userRatingsDao; @Autowired private UserCommentsDAO userCommentsDao; @Autowired private RepositoryManager repositoryManager; @Autowired private RepositoryService repositoryService; @Autowired private RepositoryEntryStatisticsDAO reStatisticsDao; @Test public void createRepositoryEntry() { RepositoryEntry re = repositoryService.create("Rei Ayanami", "-", "Statistics", "", null); dbInstance.commitAndCloseSession(); Assert.assertNotNull(re); Assert.assertNotNull(re.getStatistics()); RepositoryEntryStatistics stats = reStatisticsDao.loadStatistics(re); Assert.assertNotNull(stats); Assert.assertNotNull(stats.getKey()); Assert.assertNotNull(stats.getCreationDate()); Assert.assertNotNull(stats.getLastModified()); Assert.assertEquals(0, stats.getLaunchCounter()); Assert.assertEquals(0, stats.getDownloadCounter()); Assert.assertNull(stats.getRating()); } @Test public void updateRatingStatistics() { //create an entry Identity id = JunitTestHelper.createAndPersistIdentityAsAuthor("update-mark-"); RepositoryEntry re = repositoryService.create(id, null, "-", "Statistics", "", null, 0); dbInstance.commitAndCloseSession(); Assert.assertNotNull(re); Assert.assertNotNull(re.getStatistics()); //set a rating userRatingsDao.updateRating(id, re, null, 5); dbInstance.commitAndCloseSession(); RepositoryEntryStatistics stats = reStatisticsDao.loadStatistics(re); Assert.assertNotNull(stats); Assert.assertEquals(5d, stats.getRating(), 0.001); } @Test public void updateCommentsStatistics() { //create an entry Identity id = JunitTestHelper.createAndPersistIdentityAsAuthor("update-comment-"); RepositoryEntry re = repositoryService.create(id, null, "-", "Statistics", "", null, 0); dbInstance.commitAndCloseSession(); Assert.assertNotNull(re); Assert.assertNotNull(re.getStatistics()); //set a rating userCommentsDao.createComment(id, re, null, "Hello, a new comment"); dbInstance.commitAndCloseSession(); RepositoryEntryStatistics stats = reStatisticsDao.loadStatistics(re); Assert.assertNotNull(stats); Assert.assertEquals(1, stats.getNumOfComments()); } @Test public void incrementLaunchCounter() { RepositoryEntry repositoryEntry = repositoryService.create("Rei Ayanami", "-", "T1_perf1", "T1_perf1", null); final Long keyRepo = repositoryEntry.getKey(); final OLATResourceable resourceable = repositoryEntry.getOlatResource(); Assert.assertNotNull(resourceable); dbInstance.closeSession(); assertEquals("Launch counter was not 0", 0, repositoryEntry.getStatistics().getLaunchCounter() ); final int mainLoop = 10; final int loop = 50; // 10 * 50 = 500 for (int m = 0; m < mainLoop; m++) { long startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { // 1. load RepositoryEntry RepositoryEntry repositoryEntryT1 = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementLaunchCounter(repositoryEntryT1); dbInstance.closeSession(); } long endTime = System.currentTimeMillis(); log.info("testIncrementLaunchCounter time=" + (endTime - startTime) + " for " + loop + " incrementLaunchCounter calls"); } RepositoryEntry repositoryEntry2 = repositoryManager.lookupRepositoryEntry(keyRepo); assertEquals("Wrong value of incrementLaunch counter",mainLoop * loop,repositoryEntry2.getStatistics().getLaunchCounter()); log.info("testIncrementLaunchCounter finished"); } /** * Compare async increment-call with sync 'setDscription' call. */ @Test public void incrementLaunchCounter_setDescription() { RepositoryEntry repositoryEntry = repositoryService.create("Rei Ayanami", "-", "T1_perf1b", "T1_perf1b", null); dbInstance.closeSession(); final Long keyRepo = repositoryEntry.getKey(); RepositoryEntry repositoryEntryT1 = repositoryManager.lookupRepositoryEntry(keyRepo); log.info("RepositoryManagerTest: call incrementLaunchCounter"); long t1 = System.nanoTime(); repositoryService.incrementLaunchCounter(repositoryEntryT1); long t2 = System.nanoTime(); log.info("RepositoryManagerTest: call incrementLaunchCounter DONE"); String displayName = "DisplayName_testIncrementLaunchCounterSetDescription"; String description = "Description_testIncrementLaunchCounterSetDescription"; log.info("RepositoryManagerTest: call setDescriptionAndName"); long t3 = System.nanoTime(); repositoryManager.setDescriptionAndName(repositoryEntryT1, displayName, description, null, null, null, null, null, null); long t4 = System.nanoTime(); log.info("RepositoryManagerTest: call setDescriptionAndName DONE"); log.info("RepositoryManagerTest: increments take=" + (t2 - t1) + " setDescription take=" + (t4 -t3) ); dbInstance.closeSession(); RepositoryEntry repositoryEntryT1Reloaded = RepositoryManager.getInstance().lookupRepositoryEntry(keyRepo); assertEquals("Wrong displayName value",displayName,repositoryEntryT1Reloaded.getDisplayname()); assertEquals("Wrong description value",description,repositoryEntryT1Reloaded.getDescription()); log.info("testIncrementLaunchCounterSetDescription: FINISHED"); } @Test public void incrementDownloadCounter() { RepositoryEntry repositoryEntry = repositoryService.create("Rei Ayanami", "-", "T1_perf2", "T1_perf2", null); final Long keyRepo = repositoryEntry.getKey(); final OLATResourceable resourceable = repositoryEntry.getOlatResource(); assertNotNull(resourceable); dbInstance.closeSession(); assertEquals("Download counter was not 0", 0, repositoryEntry.getStatistics().getDownloadCounter() ); final int mainLoop = 10; final int loop = 50; // 10 * 50 = 500 for (int m = 0; m < mainLoop; m++) { long startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { // 1. load RepositoryEntry RepositoryEntry repositoryEntryT1 = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementDownloadCounter(repositoryEntryT1); dbInstance.closeSession(); } long endTime = System.currentTimeMillis(); log.info("testIncrementDownloadCounter time=" + (endTime - startTime) + " for " + loop + " incrementDownloadCounter calls"); } RepositoryEntry repositoryEntry2 = repositoryManager.lookupRepositoryEntry(keyRepo); assertEquals("Wrong value of incrementLaunch counter",mainLoop * loop,repositoryEntry2.getStatistics().getDownloadCounter()); log.info("testIncrementDownloadCounter finished"); } /** * Test concurrent increment of the launch counter */ @Test public void concurrentIncrementLaunchCounter() { final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1)); final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1)); final int loop = 100; final int numberOfThreads = 3; final RepositoryEntry repositoryEntry = repositoryService.create("Rei Ayanami", "-", "T1_concurrent1", "T1_concurrent1", null); final Long keyRepo = repositoryEntry.getKey(); assertNotNull(repositoryEntry.getOlatResource()); dbInstance.commitAndCloseSession(); assertEquals("Launch counter was not 0", 0, repositoryEntry.getStatistics().getLaunchCounter() ); long startTime = System.currentTimeMillis(); final CountDownLatch doneSignal = new CountDownLatch(3); // start thread 1 : incrementLaunchCounter / setAccess Thread thread1 = new Thread(){ public void run() { try { sleep(10); for (int i = 1; i <= loop; i++) { // 1. load RepositoryEntry RepositoryEntry re = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementLaunchCounter(re); if (i % 20 == 0 ) { re = repositoryManager.setAccess(re, 4, false); assertEquals("Wrong access value", 4, re.getAccess()); } else if (i % 10 == 0 ) { re = repositoryManager.setAccess(re, 1, false); assertEquals("Wrong access value", 1,re.getAccess()); } dbInstance.commitAndCloseSession(); } statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { dbInstance.closeSession(); } catch (Exception e) { // ignore } doneSignal.countDown(); } } }; // start thread 2 : incrementLaunchCounter / setDescriptionAndName Thread thread2 = new Thread() { public void run() { try { sleep(10); for (int i = 1; i <= loop; i++) { // 1. load RepositoryEntry RepositoryEntry re = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementLaunchCounter(re); if (i % 25 == 0 ) { String displayName = "DisplayName" + i; String description = "Description" + i; re = repositoryManager.setDescriptionAndName(re, displayName, description, null, null, null, null, null, null); assertEquals("Wrong displayName value", displayName, re.getDisplayname()); assertEquals("Wrong description value", description, re.getDescription()); } dbInstance.commitAndCloseSession(); } statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { dbInstance.closeSession(); } catch (Exception e) { // ignore } doneSignal.countDown(); } } }; // start thread 3 Thread thread3 = new Thread() { public void run() { try { sleep(10); for (int i = 1; i <= loop; i++) { // 1. load RepositoryEntry RepositoryEntry re = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementLaunchCounter(re); if (i % 30 == 0 ) { re = repositoryManager.setAccessAndProperties(re, 3, false, true, false, false); assertEquals("Wrong access value", 3, re.getAccess()); assertEquals("Wrong canCopy value", true, re.getCanCopy()); assertEquals("Wrong getCanReference value",false, re.getCanReference()); assertEquals("Wrong getCanDownload value", false, re.getCanDownload()); } else if (i % 15 == 0 ) { re = repositoryManager.setAccessAndProperties(re, 2,false, false, true, true); assertEquals("Wrong access value", 2, re.getAccess()); assertEquals("Wrong canCopy value", false, re.getCanCopy()); assertEquals("Wrong getCanReference value", true, re.getCanReference()); assertEquals("Wrong getCanDownload value", true, re.getCanDownload()); } dbInstance.commitAndCloseSession(); } statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { dbInstance.closeSession(); } catch (Exception e) { // ignore } doneSignal.countDown(); } } }; //go! go! go! thread1.start(); thread2.start(); thread3.start(); try { boolean interrupt = doneSignal.await(30, TimeUnit.SECONDS); assertTrue("Test takes too long (more than 10s)", interrupt); } catch (InterruptedException e) { fail("" + e.getMessage()); } for(Exception e:exceptionHolder) { e.printStackTrace(); } assertEquals("Exceptions", 0, exceptionHolder.size()); RepositoryEntry re = repositoryManager.lookupRepositoryEntry(keyRepo); assertEquals("Wrong value of incrementLaunch counter", loop * numberOfThreads, re.getStatistics().getLaunchCounter()); assertEquals("DisplayName" + loop, re.getDisplayname());//check if the displayname is correct assertEquals("Description" + loop, re.getDescription()); log.info("testConcurrentIncrementLaunchCounter time=" + (System.currentTimeMillis() - startTime) + " for " + loop + " incrementLaunchCounter calls"); log.info("testConcurrentIncrementLaunchCounter finished"); } /** * Modify a repository-entry from three thread to check if the exception does not pop up. * Thread 1 : call incrementDownloadCounter after 100ms * Thread 2 : call incrementDownloadCounter after 300ms * Thread 3 : update access-value on repository-entry directly after 200ms */ @Test public void concurrentIncrementDownloadCounter() { final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1)); RepositoryEntry repositoryEntry = repositoryService.create("Rei Ayanami", "-", "T1_concurrent3", "T1_concurrent3", null); final Long keyRepo = repositoryEntry.getKey(); final OLATResourceable resourceable = repositoryEntry.getOlatResource(); assertNotNull(resourceable); final int access = 4; dbInstance.closeSession(); assertEquals("Launch counter was not 0", 0, repositoryEntry.getStatistics().getLaunchCounter() ); final CountDownLatch doneSignal = new CountDownLatch(3); // thread 1 Thread thread1 = new Thread() { public void run() { try { Thread.sleep(100); RepositoryEntry repositoryEntryT1 = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementDownloadCounter(repositoryEntryT1); log.info("testConcurrentIncrementLaunchCounterWithCodePoints: Thread1 incremented download-counter"); } catch (Exception ex) { exceptionHolder.add(ex);// no exception should happen } finally { dbInstance.commitAndCloseSession(); doneSignal.countDown(); } }}; // thread 2 Thread thread2 = new Thread() { public void run() { try { Thread.sleep(300); RepositoryEntry repositoryEntryT2 = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryService.incrementDownloadCounter(repositoryEntryT2); log.info("testConcurrentIncrementLaunchCounterWithCodePoints: Thread2 incremented download-counter"); } catch (Exception ex) { exceptionHolder.add(ex);// no exception should happen } finally { dbInstance.commitAndCloseSession(); doneSignal.countDown(); } }}; // thread 3 Thread thread3 = new Thread() { public void run() { try { Thread.sleep(200); RepositoryEntry repositoryEntryT3 = repositoryManager.lookupRepositoryEntry(keyRepo); repositoryEntryT3 = repositoryManager.setAccess(repositoryEntryT3, access, false); dbInstance.closeSession(); log.info("testConcurrentIncrementLaunchCounterWithCodePoints: Thread3 setAccess DONE"); } catch (Exception ex) { exceptionHolder.add(ex);// no exception should happen } finally { dbInstance.commitAndCloseSession(); doneSignal.countDown(); } }}; thread1.start(); thread2.start(); thread3.start(); try { boolean interrupt = doneSignal.await(10, TimeUnit.SECONDS); assertTrue("Test takes too long (more than 10s)", interrupt); } catch (InterruptedException e) { fail("" + e.getMessage()); } RepositoryEntry repositoryEntry2 = repositoryManager.lookupRepositoryEntry(keyRepo); assertEquals("Wrong value of incrementLaunch counter",2,repositoryEntry2.getStatistics().getDownloadCounter()); assertEquals("Wrong access value",access,repositoryEntry2.getAccess()); log.info("testConcurrentIncrementLaunchCounterWithCodePoints finish successful"); } }