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