/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.stateless; import junit.framework.TestCase; 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.StatelessSessionContainerInfo; import org.apache.openejb.assembler.classic.TransactionServiceInfo; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.core.LocalInitialContextFactory; import org.apache.openejb.jee.EjbJar; import org.apache.openejb.jee.StatelessBean; import javax.ejb.ConcurrentAccessTimeoutException; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @version $Revision$ $Date$ */ public class StatelessInstanceManagerPoolingTest extends TestCase { public static final AtomicInteger instances = new AtomicInteger(); public static final AtomicInteger discardedInstances = new AtomicInteger(); public void testStatelessBeanPooling() throws Exception { final InitialContext ctx = new InitialContext(); final Object object = ctx.lookup("CounterBeanLocal"); assertTrue("instanceof counter", object instanceof Counter); final CountDownLatch startPistol = new CountDownLatch(1); final CountDownLatch startingLine = new CountDownLatch(10); final CountDownLatch finishingLine = new CountDownLatch(30); final Counter counter = (Counter) object; // Do a business method... final Runnable r = new Runnable() { public void run() { counter.race(startingLine, startPistol); finishingLine.countDown(); } }; // -- READY -- // How much ever the no of client invocations the count should be 10 as only 10 instances will be created. for (int i = 0; i < 30; i++) { final Thread t = new Thread(r); t.start(); } // Wait for the beans to reach the finish line startingLine.await(1000, TimeUnit.MILLISECONDS); // -- SET -- assertEquals(10, instances.get()); // -- GO -- startPistol.countDown(); // go finishingLine.await(1000, TimeUnit.MILLISECONDS); // -- DONE -- assertEquals(10, instances.get()); } public void testStatelessBeanRelease() throws Exception { final int count = 50; final CountDownLatch invocations = new CountDownLatch(count); final InitialContext ctx = new InitialContext(); // 'count' instances should be created and discarded. for (int i = 0; i < count; i++) { final Thread thread = new Thread(new Runnable() { public void run() { Object object = null; try { object = ctx.lookup("CounterBeanLocal"); } catch (final NamingException e) { assertTrue(false); } final Counter counter = (Counter) object; assertNotNull(counter); boolean run = true; while (run) { try { counter.explode(); } catch (final javax.ejb.ConcurrentAccessTimeoutException e) { //Try again in moment... try { Thread.sleep(10); } catch (final InterruptedException ie) { //Ignore } } catch (final Exception e) { invocations.countDown(); run = false; } } } }, "test-thread-" + count); thread.setDaemon(false); thread.start(); } final boolean success = invocations.await(20, TimeUnit.SECONDS); assertTrue("invocations timeout -> invocations.getCount() == " + invocations.getCount(), success); assertEquals(count, discardedInstances.get()); } public void testStatelessBeanTimeout() throws Exception { final InitialContext ctx = new InitialContext(); final Object object = ctx.lookup("CounterBeanLocal"); assertTrue("instanceof counter", object instanceof Counter); final CountDownLatch timeouts = new CountDownLatch(10); final CountDownLatch startPistol = new CountDownLatch(1); final CountDownLatch startingLine = new CountDownLatch(10); final Counter counter = (Counter) object; // Do a business method... final Runnable r = new Runnable() { public void run() { try { counter.race(startingLine, startPistol); } catch (final ConcurrentAccessTimeoutException ex) { comment("Leap Start"); timeouts.countDown(); } } }; comment("On your mark!"); for (int i = 0; i < 20; i++) { final Thread t = new Thread(r); t.start(); } // Wait for the beans to reach the start line assertTrue("expected 10 invocations", startingLine.await(3000, TimeUnit.MILLISECONDS)); comment("Get Set!"); // Wait for the other beans timeout assertTrue("expected 10 timeouts", timeouts.await(3000, TimeUnit.MILLISECONDS)); assertEquals(10, instances.get(), 1.1); comment("Go!"); startPistol.countDown(); // go } public static Object lock = new Object[]{}; private static void comment(final String x) { // synchronized(lock){ // System.out.println(x); // System.out.flush(); // } } protected void setUp() throws Exception { super.setUp(); 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)); // containers final StatelessSessionContainerInfo statelessContainerInfo = config.configureService(StatelessSessionContainerInfo.class); statelessContainerInfo.properties.setProperty("TimeOut", "100"); statelessContainerInfo.properties.setProperty("MaxSize", "10"); statelessContainerInfo.properties.setProperty("MinSize", "2"); statelessContainerInfo.properties.setProperty("StrictPooling", "true"); assembler.createContainer(statelessContainerInfo); // Setup the descriptor information final StatelessBean bean = new StatelessBean(CounterBean.class); bean.addBusinessLocal(Counter.class.getName()); bean.addBusinessRemote(RemoteCounter.class.getName()); bean.addPostConstruct("init"); bean.addPreDestroy("destroy"); final EjbJar ejbJar = new EjbJar(); ejbJar.addEnterpriseBean(bean); instances.set(0); assembler.createApplication(config.configureApplication(ejbJar)); } @Override protected void tearDown() throws Exception { OpenEJB.destroy(); } public static interface Counter { int count(); void race(CountDownLatch ready, CountDownLatch go); void explode(); } @Remote public static interface RemoteCounter extends Counter { } public static enum Lifecycle { CONSTRUCTOR, INJECTION, POST_CONSTRUCT, BUSINESS_METHOD, PRE_DESTROY } @Stateless public static class CounterBean implements Counter, RemoteCounter { private final int count; public CounterBean() { count = instances.incrementAndGet(); } public int count() { return instances.get(); } public void explode() { final int i = discardedInstances.incrementAndGet(); throw new NullPointerException("Test expected this null pointer: " + i); } public void race(final CountDownLatch ready, final CountDownLatch go) { comment("ready = " + count); ready.countDown(); try { go.await(); comment("running = " + count); } catch (final InterruptedException e) { Thread.interrupted(); } } public void init() { } public void destroy() { } } }