/* * Copyright 2013 Robert von Burg <eitch@eitchnet.ch> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package li.strolch.xmlpers.test; import static li.strolch.xmlpers.test.impl.TestConstants.TYPE_RES; import static li.strolch.xmlpers.test.model.ModelBuilder.RES_TYPE; import static li.strolch.xmlpers.test.model.ModelBuilder.createResource; import static li.strolch.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import li.strolch.xmlpers.api.IoMode; import li.strolch.xmlpers.api.PersistenceConstants; import li.strolch.xmlpers.api.PersistenceTransaction; import li.strolch.xmlpers.objref.IdOfSubTypeRef; import li.strolch.xmlpers.objref.LockableObject; import li.strolch.xmlpers.test.model.MyModel; /** * @author Robert von Burg <eitch@eitchnet.ch> * */ public class LockingTest extends AbstractPersistenceTest { private static final String BASE_PATH = "target/db/LockingTest/"; //$NON-NLS-1$ private long waitForWorkersTime; private boolean run; @BeforeClass public static void beforeClass() { cleanPath(BASE_PATH); } @Before public void before() { Properties properties = new Properties(); properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); setup(properties); this.waitForWorkersTime = LockableObject.getLockTime() + getWaitForWorkersTime() + 300L; } @Test public void shouldLockObjects() throws InterruptedException { List<CreateResourceWorker> workers = new ArrayList<>(5); String resoureId = "worker"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { String workerName = resoureId + "_" + i; //$NON-NLS-1$ CreateResourceWorker worker = new CreateResourceWorker(workerName, workerName); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ } int nrOfSuccess = runWorkers(workers); assertEquals("Only one thread should be able to perform the TX!", 5, nrOfSuccess); //$NON-NLS-1$ } @Test public void shouldFailIfResourceAlreadyExists() throws InterruptedException { List<CreateResourceWorker> workers = new ArrayList<>(5); String resourceId = "createWorkerRes"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { String workerName = resourceId + "_" + i; //$NON-NLS-1$ CreateResourceWorker worker = new CreateResourceWorker(workerName, resourceId); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ } int nrOfSuccess = runWorkers(workers); assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ } @Test public void shouldFailUpdateIfLockNotAcquirable() throws InterruptedException { // prepare workers List<UpdateResourceWorker> workers = new ArrayList<>(5); String resourceId = "updatWorkerRes"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { String workerName = resourceId + "_" + i; //$NON-NLS-1$ UpdateResourceWorker worker = new UpdateResourceWorker(workerName, resourceId); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ } // create resource which is to be updated try (PersistenceTransaction tx = this.persistenceManager.openTx()) { MyModel resource = createResource(resourceId); tx.getObjectDao().add(resource); } int nrOfSuccess = runWorkers(workers); assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ } private int runWorkers(List<? extends AbstractWorker> workers) throws InterruptedException { setRun(true); for (AbstractWorker worker : workers) { worker.join(getWaitForWorkersTime() + 2000L); } int nrOfSuccess = 0; for (AbstractWorker worker : workers) { if (worker.isSuccess()) nrOfSuccess++; } return nrOfSuccess; } public long getWaitForWorkersTime() { return this.waitForWorkersTime; } public boolean isRun() { return this.run; } public void setRun(boolean run) { this.run = run; } public abstract class AbstractWorker extends Thread { protected boolean success; protected String resourceId; public AbstractWorker(String name, String resourceId) { super(name); this.resourceId = resourceId; } @Override public void run() { logger.info("Waiting for ok to work..."); //$NON-NLS-1$ while (!isRun()) { try { Thread.sleep(10L); } catch (InterruptedException e) { throw new RuntimeException(e); } } logger.info("Starting work..."); //$NON-NLS-1$ try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) { doWork(tx); try { Thread.sleep(getWaitForWorkersTime()); } catch (InterruptedException e) { throw new RuntimeException(e); } this.success = true; } logger.info("Work completed."); //$NON-NLS-1$ } protected abstract void doWork(PersistenceTransaction tx); public boolean isSuccess() { return this.success; } } public class CreateResourceWorker extends AbstractWorker { public CreateResourceWorker(String name, String resourceId) { super(name, resourceId); } @Override protected void doWork(PersistenceTransaction tx) { MyModel resource = createResource(this.resourceId); tx.getObjectDao().add(resource); } } public class UpdateResourceWorker extends AbstractWorker { public UpdateResourceWorker(String name, String resourceId) { super(name, resourceId); } @Override protected void doWork(PersistenceTransaction tx) { IdOfSubTypeRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, RES_TYPE, this.resourceId); MyModel resource = tx.getObjectDao().queryById(objectRef); assertNotNull(resource); updateResource(resource); tx.getObjectDao().update(resource); } } }