/*
* Copyright 2011 The Apache OpenEJB development community.
*
* 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 org.apache.openejb.core.stateful;
import org.apache.openejb.OpenEJB;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
import org.apache.openejb.assembler.classic.SecurityServiceInfo;
import org.apache.openejb.assembler.classic.StatefulSessionContainerInfo;
import org.apache.openejb.assembler.classic.TransactionServiceInfo;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.core.LocalInitialContextFactory;
import org.apache.openejb.jee.ConcurrentMethod;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.NamedMethod;
import org.apache.openejb.jee.StatefulBean;
import org.apache.openejb.jee.Timeout;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.ejb.Local;
import javax.ejb.Stateful;
import javax.naming.InitialContext;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Using real non-pooled multithreaded access to test concurrent code execution on a stateful bean.
* The test also employs a test for failure.
*/
public class StatefulConcurrentLookupTest {
private static final int THREAD_COUNT = 100;
@BeforeClass
public static synchronized void beforeClass() throws Exception {
System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, LocalInitialContextFactory.class.getName());
final ConfigurationFactory config = new ConfigurationFactory();
final Assembler assembler = new Assembler();
assembler.createProxyFactory(config.configureService(ProxyFactoryInfo.class));
assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class));
assembler.createSecurityService(config.configureService(SecurityServiceInfo.class));
final StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
assembler.createContainer(statefulContainerInfo);
final EjbJar ejbJar = new EjbJar();
final StatefulBean bean1 = new StatefulBean(MyLocalBeanImpl.class);
final Timeout timeout1 = new Timeout();
timeout1.setTimeout(10);
timeout1.setUnit(TimeUnit.SECONDS);
final ConcurrentMethod method1 = new ConcurrentMethod();
method1.setMethod(new NamedMethod("*"));
method1.setAccessTimeout(timeout1);
bean1.getConcurrentMethod().add(method1);
ejbJar.addEnterpriseBean(bean1);
assembler.createApplication(config.configureApplication(ejbJar));
}
@AfterClass
public static void afterClass() throws Exception {
OpenEJB.destroy();
}
@Test
public void testLookup() throws Exception {
runScenario(false);
}
@Test
public void testLookupWithFail() throws Exception {
runScenario(true);
}
private void runScenario(final boolean throwException) throws InterruptedException {
final CountDownLatch startingLine = new CountDownLatch(THREAD_COUNT);
final CountDownLatch finishingLine = new CountDownLatch(THREAD_COUNT);
final List<TestRunnable> runnables = new ArrayList<TestRunnable>();
int i = 0;
for (; i < THREAD_COUNT; i++) {
final TestRunnable runnable = new TestRunnable("Lookup." + i, throwException, startingLine, finishingLine);
runnables.add(runnable);
final Thread thread = new Thread(runnable);
thread.setDaemon(true);
thread.start();
}
assertTrue("Threads failed to start", startingLine.await(30, TimeUnit.SECONDS));
assertTrue("Threads failed to finish", finishingLine.await(30, TimeUnit.SECONDS));
for (final TestRunnable runnable : runnables) {
if (!throwException == runnable.isSuccess()) {
i--;
}
}
assertEquals(THREAD_COUNT, (THREAD_COUNT - i));
}
private static class TestRunnable implements Runnable {
private final CountDownLatch startingLine;
private final CountDownLatch finishingLine;
private final String name;
private final boolean throwException;
private volatile boolean success = false;
private TestRunnable(final String name, final boolean throwException, final CountDownLatch startingLine, final CountDownLatch finishingLine) {
this.name = name;
this.throwException = throwException;
this.startingLine = startingLine;
this.finishingLine = finishingLine;
}
@Override
public void run() {
startingLine.countDown();
try {
startingLine.await();
final InitialContext ctx = new InitialContext();
final MyLocalBean bean = (MyLocalBean) ctx.lookup("MyLocalBeanImplLocal");
bean.set(name, throwException);
success = name.equals(bean.get());
} catch (final Throwable t) {
success = false;
} finally {
finishingLine.countDown();
}
}
public boolean isSuccess() {
return success;
}
}
@Local
public static interface MyLocalBean {
void set(final String txt, final boolean throwException);
String get();
}
@Stateful
public static class MyLocalBeanImpl implements MyLocalBean {
private String txt = "default";
private boolean throwException = false;
@Override
public void set(final String txt, final boolean throwException) {
this.txt = txt;
this.throwException = throwException;
}
@Override
public String get() {
if (this.throwException) {
throw new UnsupportedOperationException(this.txt + " - This is an expected test Exception");
}
return this.txt;
}
}
}