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