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