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    }