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