/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <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> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.commons.persistence; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Date; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.persistence.EntityManager; import org.junit.Assert; import org.junit.Test; import org.olat.basesecurity.model.GroupImpl; import org.olat.core.logging.DBRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.properties.Property; import org.olat.properties.PropertyManager; import org.olat.repository.RepositoryManager; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; /** * A <b>DBTest</b> is used to test the persistence package. * * @author Andreas Ch. Kapp * */ public class DBTest extends OlatTestCase { private static final OLog log = Tracing.createLoggerFor(DBTest.class); @Autowired private DBImpl dbInstance; @Autowired private RepositoryManager repositoryManager; /** * testCloseOfUninitializedSession */ @Test public void testCloseOfUninitializedSession() { //close it dbInstance.closeSession(); // and close it. dbInstance.closeSession(); } @Test public void testMergeEntityManager_transactional() { CountDownLatch latch = new CountDownLatch(1); TestThread test = new TestThread(repositoryManager, latch); test.start(); try { latch.await(20, TimeUnit.SECONDS); } catch (InterruptedException e) { fail("Takes too long (more than 20sec)"); } Assert.assertFalse(test.hasError()); } private class TestThread extends Thread { private boolean error; private final CountDownLatch latch; private final RepositoryManager repoManager; public TestThread(RepositoryManager repositoryManager, CountDownLatch latch) { this.latch = latch; this.repoManager = repositoryManager; } public boolean hasError() { return error; } public void run() { try { EntityManager em1 = dbInstance.getCurrentEntityManager(); Assert.assertNull(em1); repoManager.lookupRepositoryEntry(27l, false); EntityManager em2 = dbInstance.getCurrentEntityManager(); //Transactional annotation must clean-up the entity manager Assert.assertNull(em2); } catch (Exception e) { error = true; log.error("", e); } finally { latch.countDown(); } } } /** * testErrorHandling */ @Test public void testErrorHandling() { GroupImpl entry = new GroupImpl(); entry.setName("foo"); try { dbInstance.saveObject(entry); fail("Should generate an error"); } catch (DBRuntimeException dre) { assertTrue(dbInstance.isError()); Assert.assertNotNull(dbInstance.getError()); } //the close must clear the transaction dbInstance.closeSession(); //a second try must work GroupImpl entryTwo = new GroupImpl(); entryTwo.setName("bar"); entryTwo.setCreationDate(new Date()); dbInstance.saveObject(entryTwo); dbInstance.commitAndCloseSession(); } @Test public void testRollback() { String propertyKey = "testRollback-1"; String testValue = "testRollback-1"; try { PropertyManager pm = PropertyManager.getInstance(); Property p1 = pm.createPropertyInstance(null, null, null, null, propertyKey, null, null, testValue, null); pm.saveProperty(p1); String testValue2 = "testRollback-2"; // name is null => generated DB error => rollback Property p2 = pm.createPropertyInstance(null, null, null, null, null, null, null, testValue2, null); pm.saveProperty(p2); dbInstance.commit(); fail("Should generate error for rollback."); } catch (Exception ex) { dbInstance.closeSession(); } // check if p1 is rollbacked PropertyManager pm = PropertyManager.getInstance(); Property p =pm.findProperty(null, null, null, null, propertyKey); assertNull("Property.save is NOT rollbacked", p); } @Test public void testMixedNonTransactional_Transactional() { String propertyKey1 = "testMixed-1"; String testValue1 = "testMixed-1"; String propertyKey2 = "testMixed-2"; String testValue2 = "testMixed-2"; String testValue3 = "testMixed-3"; try { // outside of transaction PropertyManager pm = PropertyManager.getInstance(); Property p1 = pm.createPropertyInstance(null, null, null, null, propertyKey1, null, null, testValue1, null); pm.saveProperty(p1); // inside of transaction Property p2 = pm.createPropertyInstance(null, null, null, null, propertyKey2, null, null, testValue2, null); pm.saveProperty(p2); // name is null => generated DB error => rollback Property p3 = pm.createPropertyInstance(null, null, null, null, null, null, null, testValue3, null); pm.saveProperty(p3); dbInstance.commit(); fail("Should generate error for rollback."); dbInstance.closeSession(); } catch (Exception ex) { dbInstance.closeSession(); } // check if p1&p2 is rollbacked PropertyManager pm = PropertyManager.getInstance(); Property p_1 =pm.findProperty(null, null, null, null, propertyKey1); assertNull("Property1 is NOT rollbacked", p_1); Property p_2 =pm.findProperty(null, null, null, null, propertyKey2); assertNull("Property2 is NOT rollbacked", p_2); } @Test public void testRollbackNonTransactional() { String propertyKey1 = "testNonTransactional-1"; String testValue1 = "testNonTransactional-1"; String propertyKey2 = "testNonTransactional-2"; String testValue2 = "testNonTransactional-2"; String testValue3 = "testNonTransactional-3"; try { PropertyManager pm = PropertyManager.getInstance(); Property p1 = pm.createPropertyInstance(null, null, null, null, propertyKey1, null, null, testValue1, null); pm.saveProperty(p1); Property p2 = pm.createPropertyInstance(null, null, null, null, propertyKey2, null, null, testValue2, null); pm.saveProperty(p2); // name is null => generated DB error => rollback ? Property p3 = pm.createPropertyInstance(null, null, null, null, null, null, null, testValue3, null); pm.saveProperty(p3); dbInstance.commit(); fail("Should generate error for rollback."); dbInstance.closeSession(); } catch (Exception ex) { dbInstance.closeSession(); } // check if p1 & p2 is NOT rollbacked PropertyManager pm = PropertyManager.getInstance(); Property p_1 =pm.findProperty(null, null, null, null, propertyKey1); assertNull("Property1 is NOT rollbacked", p_1); Property p_2 =pm.findProperty(null, null, null, null, propertyKey2); assertNull("Property2 is NOT rollbacked", p_2); } @Test public void testDbPerf() { int loops = 1000; long timeWithoutTransction = 0; log.info("start testDbPerf with loops=" + loops); try { long startTime = System.currentTimeMillis(); for (int loopCounter=0; loopCounter<loops; loopCounter++) { String propertyKey = "testDbPerfKey-" + loopCounter; PropertyManager pm = PropertyManager.getInstance(); String testValue = "testDbPerfValue-" + loopCounter; Property p = pm.createPropertyInstance(null, null, null, null, propertyKey, null, null, testValue, null); pm.saveProperty(p); // forget session cache etc. dbInstance.closeSession(); pm.deleteProperty(p); } long endTime = System.currentTimeMillis(); timeWithoutTransction = endTime - startTime; log.info("testDbPerf without transaction takes :" + timeWithoutTransction + "ms"); } catch (Exception ex) { fail("Exception in testDbPerf without transaction ex="+ ex); } try { long startTime = System.currentTimeMillis(); for (int loopCounter=0; loopCounter<loops; loopCounter++) { String propertyKey = "testDbPerfKey-" + loopCounter; PropertyManager pm = PropertyManager.getInstance(); String testValue = "testDbPerfValue-" + loopCounter; Property p = pm.createPropertyInstance(null, null, null, null, propertyKey, null, null, testValue, null); pm.saveProperty(p); // forget session cache etc. dbInstance.closeSession(); pm.deleteProperty(p); } long endTime = System.currentTimeMillis(); long timeWithTransction = endTime - startTime; log.info("testDbPerf with transaction takes :" + timeWithTransction + "ms"); log.info("testDbPerf diff between transaction and without transaction :" + (timeWithTransction - timeWithoutTransction) + "ms"); } catch (Exception ex) { fail("Exception in testDbPerf with transaction ex="+ ex); } } @Test public void testDBUTF8capable() { PropertyManager pm = PropertyManager.getInstance(); String uuid = UUID.randomUUID().toString(); String unicodetest = "a-greek a\u03E2a\u03EAa\u03E8 arab \u0630a\u0631 chinese:\u3150a\u3151a\u3152a\u3153a\u3173a\u3110-z"; Property p = pm.createPropertyInstance(null, null, null, null, uuid, null, null, unicodetest, null); pm.saveProperty(p); // forget session cache etc. dbInstance.closeSession(); Property p2 = pm.findProperty(null, null, null, null, uuid); String lStr = p2.getStringValue(); assertEquals(unicodetest, lStr); } @Test public void testFindObject() { // 1. create a property to have an object Property p = PropertyManager.getInstance().createPropertyInstance(null, null, null, null, "testFindObject", null, null, "testFindObject_Value", null); PropertyManager.getInstance().saveProperty(p); long propertyKey = p.getKey(); // forget session cache etc. dbInstance.closeSession(); // 2. try to find object Object testObject = dbInstance.findObject(Property.class, propertyKey); assertNotNull(testObject); // 3. Delete object PropertyManager.getInstance().deleteProperty( (Property)testObject ); dbInstance.closeSession(); // 4. try again to find object, now no-one should be found, must return null testObject = DBFactory.getInstance().findObject(Property.class, propertyKey); assertNull(testObject); } /** * Test concurrent updating. DbWorker threads updates concurrent db. */ @Test public void testConcurrentUpdate() { int maxWorkers = 5; int loops = 100; log.info("start testConcurrentUpdate maxWorkers=" + maxWorkers + " loops=" + loops); CountDownLatch latch = new CountDownLatch(maxWorkers); DbWorker[] dbWorkers = new DbWorker[maxWorkers]; for (int i=0; i<maxWorkers; i++) { dbWorkers[i] = new DbWorker(i,loops, latch); } for (int i=0; i<maxWorkers; i++) { dbWorkers[i].start(); } // sleep until t1 and t2 should have terminated/excepted try { latch.await(20, TimeUnit.SECONDS); } catch (InterruptedException e) { fail("Takes too long (more than 20sec)"); } for (int i=0; i<maxWorkers; i++) { assertEquals(0,dbWorkers[i].getErrorCounter()); } log.info("finished testConcurrentUpdate "); } private class DbWorker extends Thread { private int numberOfLoops; private String workerId; private int errorCounter = 0; private final CountDownLatch latch; public DbWorker(int id, int numberOfLoops, CountDownLatch latch) { this.latch = latch; this.numberOfLoops = numberOfLoops; this.workerId = Integer.toString(id); } public void run() { int loopCounter = 0; try { Thread.sleep(10); while (loopCounter++ < numberOfLoops ) { String propertyKey = UUID.randomUUID().toString(); PropertyManager pm = PropertyManager.getInstance(); String testValue = "DbWorkerValue-" + workerId + "-" + loopCounter; Property p = pm.createPropertyInstance(null, null, null, null, propertyKey, null, null, testValue, null); pm.saveProperty(p); // forget session cache etc. dbInstance.closeSession(); Property p2 = pm.findProperty(null, null, null, null, propertyKey); String lStr = p2.getStringValue(); if (!testValue.equals(lStr)) { errorCounter++; } dbInstance.closeSession(); } } catch (Exception ex) { log.error("", ex); errorCounter++; } finally { latch.countDown(); } } protected int getErrorCounter() { return errorCounter; } } }