/*
* 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.service.test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import li.strolch.model.Locator;
import li.strolch.model.Resource;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
import li.strolch.service.api.ServiceResultState;
import li.strolch.testbase.runtime.RuntimeMock;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class LockingTest {
private static final String RUNTIME_PATH = "target/lockingTest/"; //$NON-NLS-1$
private static final String CONFIG_SRC = "src/test/resources/transienttest"; //$NON-NLS-1$
private static final String RESOURCE_LOCATOR = "Resource/TestType/MyTestResource";
protected static RuntimeMock runtimeMock;
private volatile boolean run;
@BeforeClass
public static void beforeClass() {
File rootPath = new File(RUNTIME_PATH);
File configSrc = new File(CONFIG_SRC);
runtimeMock = new RuntimeMock();
runtimeMock.mockRuntime(rootPath, configSrc);
runtimeMock.startContainer();
}
@AfterClass
public static void afterClass() {
runtimeMock.destroyRuntime();
}
public static ServiceHandler getServiceHandler() {
return runtimeMock.getContainer().getComponent(ServiceHandler.class);
}
public static Certificate login() {
return runtimeMock.getPrivilegeHandler().authenticate("test", "test".getBytes());
}
@Test
public void shouldLockElements() throws InterruptedException {
List<LockingRunner> runners = new ArrayList<>();
// create the long running service
LockingServiceTest longRunningService = new LockingServiceTest();
LockingArgumentTest longRunningArg = new LockingArgumentTest();
longRunningArg.longRunning = true;
longRunningArg.resourceLoc = Locator.valueOf(RESOURCE_LOCATOR);
runners.add(new LockingRunner(longRunningService, longRunningArg));
// create multiple services which try and modify the same service, but are not long running
for (int i = 0; i < 5; i++) {
LockingServiceTest svc = new LockingServiceTest();
LockingArgumentTest arg = new LockingArgumentTest();
arg.longRunning = false;
arg.resourceLoc = Locator.valueOf(RESOURCE_LOCATOR);
runners.add(new LockingRunner(svc, arg));
}
runRunners(runners);
// now assert that we can perform another such service, thus validating that the resource is not locked any longer
doLockServiceTest(false);
}
private void runRunners(List<LockingRunner> runners) throws InterruptedException {
this.run = false;
for (LockingRunner lockingRunner : runners) {
lockingRunner.start();
}
this.run = true;
for (LockingRunner lockingRunner : runners) {
lockingRunner.join();
}
assertNotNull(runners.get(0).getResult());
assertEquals(ServiceResultState.SUCCESS, runners.get(0).getResult().getState());
for (int i = 1; i < runners.size(); i++) {
ServiceResult result = runners.get(i).getResult();
assertEquals(ServiceResultState.FAILED, result.getState());
assertThat(result.getMessage(), containsString("Failed to acquire lock after"));
}
}
@Test
public void shouldUnlockCompletelyOnMultipleLock() throws InterruptedException {
List<LockingRunner> runners = new ArrayList<>();
LockingServiceTest svc = new LockingServiceTest();
LockingArgumentTest arg = new LockingArgumentTest();
arg.longRunning = false;
arg.nrOfLocks = 5;
arg.resourceLoc = Locator.valueOf(RESOURCE_LOCATOR);
runners.add(new LockingRunner(svc, arg));
runRunners(runners);
// now assert that we can perform another such service, thus validating that the resource is not locked any longer
doLockServiceTest(false);
}
private void doLockServiceTest(boolean longRunning) {
LockingServiceTest svc = new LockingServiceTest();
LockingArgumentTest arg = new LockingArgumentTest();
arg.longRunning = longRunning;
arg.resourceLoc = Locator.valueOf(RESOURCE_LOCATOR);
ServiceResult result = getServiceHandler().doService(login(), svc, arg);
assertEquals(ServiceResultState.SUCCESS, result.getState());
}
private class LockingRunner extends Thread {
private LockingServiceTest svc;
private LockingArgumentTest arg;
private ServiceResult result;
/**
* @param svc
* @param arg
*/
public LockingRunner(LockingServiceTest svc, LockingArgumentTest arg) {
super();
this.svc = svc;
this.arg = arg;
}
@Override
public void run() {
while (!LockingTest.this.run) {
continue;
}
this.result = getServiceHandler().doService(login(), this.svc, this.arg);
}
public ServiceResult getResult() {
return this.result;
}
}
private static class LockingArgumentTest extends ServiceArgument {
private static final long serialVersionUID = 1L;
public boolean longRunning;
public int nrOfLocks = 1;
public Locator resourceLoc;
}
private static class LockingServiceTest extends AbstractService<LockingArgumentTest, ServiceResult> {
private static final long serialVersionUID = 1L;
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
protected ServiceResult internalDoService(LockingArgumentTest arg) throws Exception {
try (StrolchTransaction tx = openTx(arg.realm)) {
if (!arg.longRunning)
Thread.sleep(200l);
Resource res = tx.findElement(arg.resourceLoc);
for (int i = 0; i < arg.nrOfLocks; i++)
tx.lock(res);
if (arg.longRunning)
Thread.sleep(5000l);
tx.commitOnClose();
}
return ServiceResult.success();
}
}
}