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