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 LifecycleStrategy strategy = new LifecycleStrategy() { 311 public void start(Object component) { 312 ((StringBuffer)component).append("start>"); 313 } 314 315 public void stop(Object component) { 316 ((StringBuffer)component).append("stop>"); 317 } 318 319 public void dispose(Object component) { 320 ((StringBuffer)component).append("dispose>"); 321 } 322 323 public boolean hasLifecycle(Class type) { 324 return true; 325 } 326 327 public boolean isLazy(ComponentAdapter<?> adapter) { 328 return false; 329 } 330 }; 331 MutablePicoContainer pico = new DefaultPicoContainer( new AdaptingInjection(), strategy, null ); 332 333 StringBuffer sb = new StringBuffer(); 334 335 pico.addComponent(sb); 336 337 pico.start(); 338 pico.stop(); 339 pico.dispose(); 340 341 assertEquals("start>stop>dispose>", sb.toString()); 342 } 343 344 @Test public void testLifeCycleStrategyForInstanceRegistrationPassedToChildContainers() 345 throws Exception 346 { 347 LifecycleStrategy strategy = new LifecycleStrategy() { 348 public void start(Object component) { 349 ((StringBuffer)component).append("start>"); 350 } 351 352 public void stop(Object component) { 353 ((StringBuffer)component).append("stop>"); 354 } 355 356 public void dispose(Object component) { 357 ((StringBuffer)component).append("dispose>"); 358 } 359 360 public boolean hasLifecycle(Class type) { 361 return true; 362 } 363 364 public boolean isLazy(ComponentAdapter<?> adapter) { 365 return false; 366 } 367 }; 368 MutablePicoContainer parent = new DefaultPicoContainer(strategy, null); 369 MutablePicoContainer pico = parent.makeChildContainer(); 370 371 StringBuffer sb = new StringBuffer(); 372 373 pico.addComponent(sb); 374 375 pico.start(); 376 pico.stop(); 377 pico.dispose(); 378 379 assertEquals("start>stop>dispose>", sb.toString()); 380 } 381 382 383 @Test public void testLifecycleDoesNotRecoverWithNullComponentMonitor() { 384 385 final Startable s1 = mockery.mock(Startable.class, "s1"); 386 Startable s2 = mockery.mock(Startable.class, "s2"); 387 mockery.checking(new Expectations(){{ 388 one(s1).start(); 389 will(throwException(new RuntimeException("I do not want to start myself"))); 390 }}); 391 392 DefaultPicoContainer dpc = new DefaultPicoContainer(); 393 dpc.addComponent("foo", s1); 394 dpc.addComponent("bar", s2); 395 try { 396 dpc.start(); 397 fail("PicoLifecylceException expected"); 398 } catch (PicoLifecycleException e) { 399 assertEquals("I do not want to start myself", e.getCause().getMessage()); 400 } 401 dpc.stop(); 402 } 403 404 @Test public void testLifecycleCanRecoverWithCustomComponentMonitor() throws NoSuchMethodException { 405 406 final Startable s1 = mockery.mock(Startable.class, "s1"); 407 final Startable s2 = mockery.mock(Startable.class, "s2"); 408 final ComponentMonitor cm = mockery.mock(ComponentMonitor.class); 409 mockery.checking(new Expectations(){{ 410 one(s1).start(); 411 will(throwException(new RuntimeException("I do not want to start myself"))); 412 one(s1).stop(); 413 one(s2).start(); 414 one(s2).stop(); 415 // s1 expectations 416 one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("start", (Class[])null))), with(same(s1)), with(any(Object[].class))); 417 one(cm).lifecycleInvocationFailed(with(aNull(MutablePicoContainer.class)), with(aNull(ComponentAdapter.class)), with(any(Method.class)), with(same(s1)), with(any(RuntimeException.class))); 418 one(cm).invoking(with(aNull(PicoContainer.class)), 419 with(aNull(ComponentAdapter.class)), 420 with(equal(Startable.class.getMethod("stop", (Class[])null))), 421 with(same(s1)), with(any(Object[].class))); 422 one(cm).invoked(with(aNull(PicoContainer.class)), 423 with(aNull(ComponentAdapter.class)), 424 with(equal(Startable.class.getMethod("stop", (Class[])null))), 425 with(same(s1)), with(any(Long.class)), with(any(Object[].class)), with(same(null))); 426 // s2 expectations 427 one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("start", (Class[])null))), with(same(s2)), with(any(Object[].class))); 428 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)), with(any(Object[].class)), with(same(null))); 429 one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("stop", (Class[])null))), with(same(s2)), with(any(Object[].class))); 430 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)), with(any(Object[].class)), with(same(null))); 431 }}); 432 433 DefaultPicoContainer dpc = new DefaultPicoContainer(cm); 434 dpc.addComponent("foo", s1); 435 dpc.addComponent("bar", s2); 436 dpc.start(); 437 dpc.stop(); 438 } 439 440 @Test public void testLifecycleFailuresCanBePickedUpAfterTheEvent() { 441 final Startable s1 = mockery.mock(Startable.class, "s1"); 442 final Startable s2 = mockery.mock(Startable.class, "s2"); 443 final Startable s3 = mockery.mock(Startable.class, "s3"); 444 mockery.checking(new Expectations(){{ 445 one(s1).start(); 446 will(throwException(new RuntimeException("I do not want to start myself"))); 447 one(s1).stop(); 448 one(s2).start(); 449 one(s2).stop(); 450 one(s3).start(); 451 will(throwException(new RuntimeException("I also do not want to start myself"))); 452 one(s3).stop(); 453 }}); 454 455 LifecycleComponentMonitor lifecycleComponentMonitor = new LifecycleComponentMonitor(new NullComponentMonitor()); 456 457 DefaultPicoContainer dpc = new DefaultPicoContainer(lifecycleComponentMonitor); 458 dpc.addComponent("one", s1); 459 dpc.addComponent("two", s2); 460 dpc.addComponent("three", s3); 461 462 dpc.start(); 463 464 try { 465 lifecycleComponentMonitor.rethrowLifecycleFailuresException(); 466 fail("LifecycleFailuresException expected"); 467 } catch (LifecycleFailuresException e) { 468 assertEquals("I do not want to start myself; I also do not want to start myself;", e.getMessage().trim()); 469 dpc.stop(); 470 assertEquals(2, e.getFailures().size()); 471 } 472 473 } 474 475 @Test public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStart() { 476 477 final Startable s1 = mockery.mock(Startable.class, "s1"); 478 final Startable s2 = mockery.mock(Startable.class, "s2"); 479 mockery.checking(new Expectations(){{ 480 one(s1).start(); 481 one(s1).stop(); 482 one(s2).start(); 483 will(throwException(new RuntimeException("I do not want to start myself"))); 484 // s2 does not expect stop(). 485 }}); 486 487 DefaultPicoContainer dpc = new DefaultPicoContainer(); 488 dpc.addComponent("foo", s1); 489 dpc.addComponent("bar", s2); 490 491 try { 492 dpc.start(); 493 fail("PicoLifecylceException expected"); 494 } catch (RuntimeException e) { 495 dpc.stop(); 496 } 497 498 } 499 500 @Test public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStartEvenInAPicoHierarchy() { 501 502 final Startable s1 = mockery.mock(Startable.class, "s1"); 503 final Startable s2 = mockery.mock(Startable.class, "s2"); 504 mockery.checking(new Expectations(){{ 505 one(s1).start(); 506 one(s1).stop(); 507 one(s2).start(); 508 will(throwException(new RuntimeException("I do not want to start myself"))); 509 // s2 does not expect stop(). 510 }}); 511 512 DefaultPicoContainer dpc = new DefaultPicoContainer(); 513 dpc.addComponent("foo", s1); 514 dpc.addComponent("bar", s2); 515 dpc.addChildContainer(new DefaultPicoContainer(dpc)); 516 517 try { 518 dpc.start(); 519 fail("PicoLifecylceException expected"); 520 } catch (RuntimeException e) { 521 dpc.stop(); 522 } 523 524 } 525 526 @Test public void testChildContainerIsStoppedWhenStartedIndependentlyOfParent() throws Exception { 527 528 DefaultPicoContainer parent = new DefaultPicoContainer(); 529 530 parent.start(); 531 532 MutablePicoContainer child = parent.makeChildContainer(); 533 534 final Startable s1 = mockery.mock(Startable.class, "s1"); 535 mockery.checking(new Expectations(){{ 536 one(s1).start(); 537 one(s1).stop(); 538 }}); 539 540 child.addComponent(s1); 541 542 child.start(); 543 parent.stop(); 544 545 } 546 }