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    }