001 /***************************************************************************** 002 * Copyright (c) PicoContainer Organization. All rights reserved. * 003 * ------------------------------------------------------------------------- * 004 * The software in this package is published under the terms of the BSD * 005 * style license a copy of which has been included with this distribution in * 006 * the license.html file. * 007 * * 008 * Idea by Rachel Davies, Original code by Aslak Hellesoy and Paul Hammant * 009 *****************************************************************************/ 010 011 package org.picocontainer.defaults; 012 013 import junit.framework.Assert; 014 import org.jmock.Mock; 015 import org.jmock.MockObjectTestCase; 016 import org.picocontainer.ComponentMonitor; 017 import org.picocontainer.MutablePicoContainer; 018 import org.picocontainer.PicoContainer; 019 import org.picocontainer.PicoLifecycleException; 020 import org.picocontainer.Startable; 021 import org.picocontainer.monitors.LifecycleComponentMonitor; 022 import org.picocontainer.monitors.LifecycleComponentMonitor.LifecycleFailuresException; 023 import org.picocontainer.testmodel.RecordingLifecycle.FiveTriesToBeMalicious; 024 import org.picocontainer.testmodel.RecordingLifecycle.Four; 025 import org.picocontainer.testmodel.RecordingLifecycle.One; 026 import org.picocontainer.testmodel.RecordingLifecycle.Three; 027 import org.picocontainer.testmodel.RecordingLifecycle.Two; 028 029 import java.lang.reflect.Method; 030 import java.util.ArrayList; 031 import java.util.HashMap; 032 import java.util.List; 033 034 /** 035 * This class tests the lifecycle aspects of DefaultPicoContainer. 036 * 037 * @author Aslak Hellesøy 038 * @author Paul Hammant 039 * @author Ward Cunningham 040 * @version $Revision: 2822 $ 041 */ 042 public class DefaultPicoContainerLifecycleTestCase extends MockObjectTestCase { 043 044 045 public void testOrderOfInstantiationShouldBeDependencyOrder() throws Exception { 046 047 DefaultPicoContainer pico = new DefaultPicoContainer(); 048 pico.registerComponentImplementation("recording", StringBuffer.class); 049 pico.registerComponentImplementation(Four.class); 050 pico.registerComponentImplementation(Two.class); 051 pico.registerComponentImplementation(One.class); 052 pico.registerComponentImplementation(Three.class); 053 final List componentInstances = pico.getComponentInstances(); 054 055 // instantiation - would be difficult to do these in the wrong order!! 056 assertEquals("Incorrect Order of Instantiation", One.class, componentInstances.get(1).getClass()); 057 assertEquals("Incorrect Order of Instantiation", Two.class, componentInstances.get(2).getClass()); 058 assertEquals("Incorrect Order of Instantiation", Three.class, componentInstances.get(3).getClass()); 059 assertEquals("Incorrect Order of Instantiation", Four.class, componentInstances.get(4).getClass()); 060 } 061 062 public void testOrderOfStartShouldBeDependencyOrderAndStopAndDisposeTheOpposite() throws Exception { 063 DefaultPicoContainer parent = new DefaultPicoContainer(); 064 MutablePicoContainer child = parent.makeChildContainer(); 065 066 parent.registerComponentImplementation("recording", StringBuffer.class); 067 child.registerComponentImplementation(Four.class); 068 parent.registerComponentImplementation(Two.class); 069 parent.registerComponentImplementation(One.class); 070 child.registerComponentImplementation(Three.class); 071 072 parent.start(); 073 parent.stop(); 074 parent.dispose(); 075 076 assertEquals("<One<Two<Three<FourFour>Three>Two>One>!Four!Three!Two!One", 077 parent.getComponentInstance("recording").toString()); 078 } 079 080 081 public void testLifecycleIsIgnoredIfAdaptersAreNotLifecycleManagers() { 082 DefaultPicoContainer parent = new DefaultPicoContainer(new ConstructorInjectionComponentAdapterFactory()); 083 MutablePicoContainer child = parent.makeChildContainer(); 084 085 parent.registerComponentImplementation("recording", StringBuffer.class); 086 child.registerComponentImplementation(Four.class); 087 parent.registerComponentImplementation(Two.class); 088 parent.registerComponentImplementation(One.class); 089 child.registerComponentImplementation(Three.class); 090 091 parent.start(); 092 parent.stop(); 093 parent.dispose(); 094 095 assertEquals("", 096 parent.getComponentInstance("recording").toString()); 097 } 098 099 public void testStartStartShouldFail() throws Exception { 100 DefaultPicoContainer pico = new DefaultPicoContainer(); 101 pico.start(); 102 try { 103 pico.start(); 104 fail("Should have failed"); 105 } catch (IllegalStateException e) { 106 // expected; 107 } 108 } 109 110 public void testStartStopStopShouldFail() throws Exception { 111 DefaultPicoContainer pico = new DefaultPicoContainer(); 112 pico.start(); 113 pico.stop(); 114 try { 115 pico.stop(); 116 fail("Should have failed"); 117 } catch (IllegalStateException e) { 118 // expected; 119 } 120 } 121 122 public void testStartStopDisposeDisposeShouldFail() throws Exception { 123 DefaultPicoContainer pico = new DefaultPicoContainer(); 124 pico.start(); 125 pico.stop(); 126 pico.dispose(); 127 try { 128 pico.dispose(); 129 fail("Should have barfed"); 130 } catch (IllegalStateException e) { 131 // expected; 132 } 133 } 134 135 public static class FooRunnable implements Runnable, Startable { 136 private int runCount; 137 private Thread thread = new Thread(); 138 private boolean interrupted; 139 140 public FooRunnable() { 141 } 142 143 public int runCount() { 144 return runCount; 145 } 146 147 public boolean isInterrupted() { 148 return interrupted; 149 } 150 151 public void start() { 152 thread = new Thread(this); 153 thread.start(); 154 } 155 156 public void stop() { 157 thread.interrupt(); 158 } 159 160 // this would do something a bit more concrete 161 // than counting in real life ! 162 public void run() { 163 runCount++; 164 try { 165 Thread.sleep(10000); 166 } catch (InterruptedException e) { 167 interrupted = true; 168 } 169 } 170 } 171 172 public void testStartStopOfDaemonizedThread() throws Exception { 173 DefaultPicoContainer pico = new DefaultPicoContainer(); 174 pico.registerComponentImplementation(FooRunnable.class); 175 176 pico.getComponentInstances(); 177 pico.start(); 178 Thread.sleep(100); 179 pico.stop(); 180 181 FooRunnable foo = (FooRunnable) pico.getComponentInstance(FooRunnable.class); 182 assertEquals(1, foo.runCount()); 183 pico.start(); 184 Thread.sleep(100); 185 pico.stop(); 186 assertEquals(2, foo.runCount()); 187 } 188 189 public void testGetComponentInstancesOnParentContainerHostedChildContainerDoesntReturnParentAdapter() { 190 MutablePicoContainer parent = new DefaultPicoContainer(); 191 MutablePicoContainer child = parent.makeChildContainer(); 192 assertEquals(0, child.getComponentInstances().size()); 193 } 194 195 public void testComponentsAreStartedBreadthFirstAndStoppedAndDisposedDepthFirst() { 196 MutablePicoContainer parent = new DefaultPicoContainer(); 197 parent.registerComponentImplementation(Two.class); 198 parent.registerComponentImplementation("recording", StringBuffer.class); 199 parent.registerComponentImplementation(One.class); 200 MutablePicoContainer child = parent.makeChildContainer(); 201 child.registerComponentImplementation(Three.class); 202 parent.start(); 203 parent.stop(); 204 parent.dispose(); 205 206 assertEquals("<One<Two<ThreeThree>Two>One>!Three!Two!One", parent.getComponentInstance("recording").toString()); 207 } 208 209 public void testMaliciousComponentCannotExistInAChildContainerAndSeeAnyElementOfContainerHierarchy() { 210 MutablePicoContainer parent = new DefaultPicoContainer(); 211 parent.registerComponentImplementation(Two.class); 212 parent.registerComponentImplementation("recording", StringBuffer.class); 213 parent.registerComponentImplementation(One.class); 214 parent.registerComponentImplementation(Three.class); 215 MutablePicoContainer child = parent.makeChildContainer(); 216 child.registerComponentImplementation(FiveTriesToBeMalicious.class); 217 try { 218 parent.start(); 219 fail("Thrown " + UnsatisfiableDependenciesException.class.getName() + " expected"); 220 } catch ( UnsatisfiableDependenciesException e) { 221 // FiveTriesToBeMalicious can't get instantiated as there is no PicoContainer in any component set 222 } 223 String recording = parent.getComponentInstance("recording").toString(); 224 assertEquals("<One<Two<Three", recording); 225 try { 226 child.getComponentInstanceOfType(FiveTriesToBeMalicious.class); 227 fail("Thrown " + UnsatisfiableDependenciesException.class.getName() + " expected"); 228 } catch (final UnsatisfiableDependenciesException e) { 229 // can't get instantiated as there is no PicoContainer in any component set 230 } 231 recording = parent.getComponentInstance("recording").toString(); 232 assertEquals("<One<Two<Three", recording); // still the same 233 } 234 235 236 public static class NotStartable { 237 public void start(){ 238 Assert.fail("start() should not get invoked on NonStartable"); 239 } 240 } 241 242 public void testOnlyStartableComponentsAreStartedOnStart() { 243 MutablePicoContainer pico = new DefaultPicoContainer(); 244 pico.registerComponentImplementation("recording", StringBuffer.class); 245 pico.registerComponentImplementation(One.class); 246 pico.registerComponentImplementation(NotStartable.class); 247 pico.start(); 248 pico.stop(); 249 pico.dispose(); 250 assertEquals("<OneOne>!One", pico.getComponentInstance("recording").toString()); 251 } 252 253 public void testShouldFailOnStartAfterDispose() { 254 MutablePicoContainer pico = new DefaultPicoContainer(); 255 pico.dispose(); 256 try { 257 pico.start(); 258 fail(); 259 } catch (IllegalStateException expected) { 260 } 261 } 262 263 public void testShouldFailOnStopAfterDispose() { 264 MutablePicoContainer pico = new DefaultPicoContainer(); 265 pico.dispose(); 266 try { 267 pico.stop(); 268 fail(); 269 } catch (IllegalStateException expected) { 270 } 271 } 272 273 public void testShouldStackContainersLast() { 274 // this is merely a code coverage test - but it doesn't seem to cover the StackContainersAtEndComparator 275 // fully. oh well. 276 MutablePicoContainer pico = new DefaultPicoContainer(); 277 pico.registerComponentImplementation(ArrayList.class); 278 pico.registerComponentImplementation(DefaultPicoContainer.class); 279 pico.registerComponentImplementation(HashMap.class); 280 pico.start(); 281 PicoContainer childContainer = (PicoContainer) pico.getComponentInstance(DefaultPicoContainer.class); 282 // it should be started too 283 try { 284 childContainer.start(); 285 fail(); 286 } catch (IllegalStateException e) { 287 } 288 } 289 290 public void testCanSpecifyLifeCycleStrategyForInstanceRegistrationWhenSpecifyingComponentAdapterFactory() 291 throws Exception 292 { 293 LifecycleStrategy strategy = new LifecycleStrategy() { 294 public void start(Object component) { 295 ((StringBuffer)component).append("start>"); 296 } 297 298 public void stop(Object component) { 299 ((StringBuffer)component).append("stop>"); 300 } 301 302 public void dispose(Object component) { 303 ((StringBuffer)component).append("dispose>"); 304 } 305 306 public boolean hasLifecycle(Class type) { 307 return true; 308 } 309 }; 310 MutablePicoContainer pico = new DefaultPicoContainer( new DefaultComponentAdapterFactory(), strategy, null ); 311 312 StringBuffer sb = new StringBuffer(); 313 314 pico.registerComponentInstance(sb); 315 316 pico.start(); 317 pico.stop(); 318 pico.dispose(); 319 320 assertEquals("start>stop>dispose>", sb.toString()); 321 } 322 323 public void testLifeCycleStrategyForInstanceRegistrationPassedToChildContainers() 324 throws Exception 325 { 326 LifecycleStrategy strategy = new LifecycleStrategy() { 327 public void start(Object component) { 328 ((StringBuffer)component).append("start>"); 329 } 330 331 public void stop(Object component) { 332 ((StringBuffer)component).append("stop>"); 333 } 334 335 public void dispose(Object component) { 336 ((StringBuffer)component).append("dispose>"); 337 } 338 339 public boolean hasLifecycle(Class type) { 340 return true; 341 } 342 }; 343 MutablePicoContainer parent = new DefaultPicoContainer(strategy, null); 344 MutablePicoContainer pico = parent.makeChildContainer(); 345 346 StringBuffer sb = new StringBuffer(); 347 348 pico.registerComponentInstance(sb); 349 350 pico.start(); 351 pico.stop(); 352 pico.dispose(); 353 354 assertEquals("start>stop>dispose>", sb.toString()); 355 } 356 357 358 public void testLifecycleDoesNotRecoverWithDefaultComponentMonitor() { 359 360 Mock s1 = mock(Startable.class, "s1"); 361 s1.expects(once()).method("start").will(throwException(new RuntimeException("I do not want to start myself"))); 362 363 Mock s2 = mock(Startable.class, "s2"); 364 365 DefaultPicoContainer dpc = new DefaultPicoContainer(); 366 dpc.registerComponentInstance("foo", s1.proxy()); 367 dpc.registerComponentInstance("bar", s2.proxy()); 368 try { 369 dpc.start(); 370 fail("PicoLifecylceException expected"); 371 } catch (PicoLifecycleException e) { 372 assertEquals("I do not want to start myself", e.getCause().getMessage()); 373 } 374 dpc.stop(); 375 } 376 377 public void testLifecycleCanRecoverWithCustomComponentMonitor() throws NoSuchMethodException { 378 379 Mock s1 = mock(Startable.class, "s1"); 380 s1.expects(once()).method("start").will(throwException(new RuntimeException("I do not want to start myself"))); 381 s1.expects(once()).method("stop"); 382 383 Mock s2 = mock(Startable.class, "s2"); 384 s2.expects(once()).method("start"); 385 s2.expects(once()).method("stop"); 386 387 Mock cm = mock(ComponentMonitor.class); 388 389 // s1 expectations 390 cm.expects(once()).method("invoking").with(eq(Startable.class.getMethod("start", (Class[])null)), same(s1.proxy())); 391 cm.expects(once()).method("lifecycleInvocationFailed").with(isA(Method.class),same(s1.proxy()), isA(RuntimeException.class) ); 392 cm.expects(once()).method("invoking").with(eq(Startable.class.getMethod("stop", (Class[])null)), same(s1.proxy())); 393 cm.expects(once()).method("invoked").with(eq(Startable.class.getMethod("stop", (Class[])null)), same(s1.proxy()), ANYTHING); 394 395 // s2 expectations 396 cm.expects(once()).method("invoking").with(eq(Startable.class.getMethod("start", (Class[])null)), same(s2.proxy())); 397 cm.expects(once()).method("invoked").with(eq(Startable.class.getMethod("start", (Class[])null)), same(s2.proxy()), ANYTHING); 398 cm.expects(once()).method("invoking").with(eq(Startable.class.getMethod("stop", (Class[])null)), same(s2.proxy())); 399 cm.expects(once()).method("invoked").with(eq(Startable.class.getMethod("stop", (Class[])null)), same(s2.proxy()), ANYTHING); 400 401 DefaultPicoContainer dpc = new DefaultPicoContainer((ComponentMonitor) cm.proxy()); 402 dpc.registerComponentInstance("foo", s1.proxy()); 403 dpc.registerComponentInstance("bar", s2.proxy()); 404 dpc.start(); 405 dpc.stop(); 406 } 407 408 public void testLifecycleFailuresCanBePickedUpAfterTheEvent() { 409 410 Mock s1 = mock(Startable.class, "s1"); 411 s1.expects(once()).method("start").will(throwException(new RuntimeException("I do not want to start myself"))); 412 s1.expects(once()).method("stop"); 413 414 Mock s2 = mock(Startable.class, "s2"); 415 s2.expects(once()).method("start"); 416 s2.expects(once()).method("stop"); 417 418 LifecycleComponentMonitor lifecycleComponentMonitor = new LifecycleComponentMonitor(); 419 420 DefaultPicoContainer dpc = new DefaultPicoContainer(lifecycleComponentMonitor); 421 dpc.registerComponentInstance("foo", s1.proxy()); 422 dpc.registerComponentInstance("bar", s2.proxy()); 423 424 dpc.start(); 425 426 try { 427 lifecycleComponentMonitor.rethrowLifecycleFailuresException(); 428 fail("LifecycleFailuresException expected"); 429 } catch (LifecycleFailuresException e) { 430 dpc.stop(); 431 assertEquals(1, e.getFailures().size()); 432 } 433 434 } 435 436 public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStart() { 437 438 Mock s1 = mock(Startable.class, "s1"); 439 s1.expects(once()).method("start"); 440 s1.expects(once()).method("stop"); 441 442 Mock s2 = mock(Startable.class, "s2"); 443 s2.expects(once()).method("start").will(throwException(new RuntimeException("I do not want to start myself"))); 444 // s2 does not expect stop(). 445 446 DefaultPicoContainer dpc = new DefaultPicoContainer(); 447 dpc.registerComponentInstance("foo", s1.proxy()); 448 dpc.registerComponentInstance("bar", s2.proxy()); 449 450 try { 451 dpc.start(); 452 fail("PicoLifecylceException expected"); 453 } catch (RuntimeException e) { 454 dpc.stop(); 455 } 456 457 } 458 459 public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStartEvenInAPicoHierarchy() { 460 461 Mock s1 = mock(Startable.class, "s1"); 462 s1.expects(once()).method("start"); 463 s1.expects(once()).method("stop"); 464 465 Mock s2 = mock(Startable.class, "s2"); 466 s2.expects(once()).method("start").will(throwException(new RuntimeException("I do not want to start myself"))); 467 // s2 does not expect stop(). 468 469 DefaultPicoContainer dpc = new DefaultPicoContainer(); 470 dpc.registerComponentInstance("foo", s1.proxy()); 471 dpc.registerComponentInstance("bar", s2.proxy()); 472 dpc.addChildContainer(new DefaultPicoContainer(dpc)); 473 474 try { 475 dpc.start(); 476 fail("PicoLifecylceException expected"); 477 } catch (RuntimeException e) { 478 dpc.stop(); 479 } 480 481 } 482 483 public void testChildContainerIsStoppedWhenStartedIndependentlyOfParent() throws Exception { 484 485 DefaultPicoContainer parent = new DefaultPicoContainer(); 486 487 parent.start(); 488 489 MutablePicoContainer child = parent.makeChildContainer(); 490 491 Mock s1 = mock(Startable.class, "s1"); 492 s1.expects(once()).method("start"); 493 s1.expects(once()).method("stop"); 494 495 child.registerComponentInstance(s1.proxy()); 496 497 child.start(); 498 parent.stop(); 499 500 } 501 }