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 }