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    }