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 }