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