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        {
311            LifecycleStrategy strategy = new LifecycleStrategy() {
312                public void start(Object component) {
313                    ((StringBuffer)component).append("start>");
314                }
315    
316                public void stop(Object component) {
317                    ((StringBuffer)component).append("stop>");
318                }
319    
320                public void dispose(Object component) {
321                    ((StringBuffer)component).append("dispose>");
322                }
323    
324                public boolean hasLifecycle(Class type) {
325                    return true;
326                }
327            };
328            MutablePicoContainer pico = new DefaultPicoContainer( new AdaptingInjection(), strategy, null );
329    
330            StringBuffer sb = new StringBuffer();
331    
332            pico.addComponent(sb);
333    
334            pico.start();
335            pico.stop();
336            pico.dispose();
337    
338            assertEquals("start>stop>dispose>", sb.toString());
339        }
340    
341        @Test public void testLifeCycleStrategyForInstanceRegistrationPassedToChildContainers()
342            throws Exception
343        {
344            LifecycleStrategy strategy = new LifecycleStrategy() {
345                public void start(Object component) {
346                    ((StringBuffer)component).append("start>");
347                }
348    
349                public void stop(Object component) {
350                    ((StringBuffer)component).append("stop>");
351                }
352    
353                public void dispose(Object component) {
354                    ((StringBuffer)component).append("dispose>");
355                }
356    
357                public boolean hasLifecycle(Class type) {
358                    return true;
359                }
360            };
361            MutablePicoContainer parent = new DefaultPicoContainer(strategy, null);
362            MutablePicoContainer pico = parent.makeChildContainer();
363    
364            StringBuffer sb = new StringBuffer();
365    
366            pico.addComponent(sb);
367    
368            pico.start();
369            pico.stop();
370            pico.dispose();
371    
372            assertEquals("start>stop>dispose>", sb.toString());
373        }
374    
375    
376        @Test public void testLifecycleDoesNotRecoverWithNullComponentMonitor() {
377    
378            final Startable s1 = mockery.mock(Startable.class, "s1");
379            Startable s2 = mockery.mock(Startable.class, "s2");
380            mockery.checking(new Expectations(){{
381                one(s1).start();
382                will(throwException(new RuntimeException("I do not want to start myself")));
383            }});
384     
385            DefaultPicoContainer dpc = new DefaultPicoContainer();
386            dpc.addComponent("foo", s1);
387            dpc.addComponent("bar", s2);
388            try {
389                dpc.start();
390                fail("PicoLifecylceException expected");
391            } catch (PicoLifecycleException e) {
392                assertEquals("I do not want to start myself", e.getCause().getMessage());
393            }
394            dpc.stop();
395        }
396    
397        @Test public void testLifecycleCanRecoverWithCustomComponentMonitor() throws NoSuchMethodException {
398    
399            final Startable s1 = mockery.mock(Startable.class, "s1");
400            final Startable s2 = mockery.mock(Startable.class, "s2");
401            final ComponentMonitor cm = mockery.mock(ComponentMonitor.class);
402            mockery.checking(new Expectations(){{
403                one(s1).start();
404                will(throwException(new RuntimeException("I do not want to start myself")));
405                one(s1).stop();
406                one(s2).start();
407                one(s2).stop();
408                // s1 expectations
409                one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("start", (Class[])null))), with(same(s1)));
410                one(cm).lifecycleInvocationFailed(with(aNull(MutablePicoContainer.class)), with(aNull(ComponentAdapter.class)), with(any(Method.class)), with(same(s1)), with(any(RuntimeException.class)));
411                one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("stop", (Class[])null))), with(same(s1)));
412                one(cm).invoked(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("stop", (Class[])null))), with(same(s1)), with(any(Long.class)));
413    
414                // s2 expectations
415                one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("start", (Class[])null))), with(same(s2)));
416                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)));
417                one(cm).invoking(with(aNull(PicoContainer.class)), with(aNull(ComponentAdapter.class)), with(equal(Startable.class.getMethod("stop", (Class[])null))), with(same(s2)));
418                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)));
419            }});
420    
421            DefaultPicoContainer dpc = new DefaultPicoContainer(cm);
422            dpc.addComponent("foo", s1);
423            dpc.addComponent("bar", s2);
424            dpc.start();
425            dpc.stop();
426        }
427    
428        @Test public void testLifecycleFailuresCanBePickedUpAfterTheEvent() {
429            final Startable s1 = mockery.mock(Startable.class, "s1");
430            final Startable s2 = mockery.mock(Startable.class, "s2");
431            final Startable s3 = mockery.mock(Startable.class, "s3");
432            mockery.checking(new Expectations(){{
433                one(s1).start();
434                will(throwException(new RuntimeException("I do not want to start myself")));
435                one(s1).stop();
436                one(s2).start();
437                one(s2).stop();
438                one(s3).start();
439                will(throwException(new RuntimeException("I also do not want to start myself")));
440                one(s3).stop();
441            }});
442            
443            LifecycleComponentMonitor lifecycleComponentMonitor = new LifecycleComponentMonitor(new NullComponentMonitor());
444    
445            DefaultPicoContainer dpc = new DefaultPicoContainer(lifecycleComponentMonitor);
446            dpc.addComponent("one", s1);
447            dpc.addComponent("two", s2);
448            dpc.addComponent("three", s3);
449    
450            dpc.start();
451    
452            try {
453                lifecycleComponentMonitor.rethrowLifecycleFailuresException();
454                fail("LifecycleFailuresException expected");
455            } catch (LifecycleFailuresException e) {
456                assertEquals("I do not want to start myself;  I also do not want to start myself;", e.getMessage().trim());
457                dpc.stop();
458                assertEquals(2, e.getFailures().size());
459            }
460    
461        }
462    
463        @Test public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStart() {
464    
465            final Startable s1 = mockery.mock(Startable.class, "s1");
466            final Startable s2 = mockery.mock(Startable.class, "s2");
467            mockery.checking(new Expectations(){{
468                one(s1).start();
469                one(s1).stop();
470                one(s2).start();
471                will(throwException(new RuntimeException("I do not want to start myself")));
472             // s2 does not expect stop().
473            }});
474            
475            DefaultPicoContainer dpc = new DefaultPicoContainer();
476            dpc.addComponent("foo", s1);
477            dpc.addComponent("bar", s2);
478    
479            try {
480                dpc.start();
481                fail("PicoLifecylceException expected");
482            } catch (RuntimeException e) {
483                dpc.stop();
484            }
485    
486        }
487    
488        @Test public void testStartedComponentsCanBeStoppedIfSomeComponentsFailToStartEvenInAPicoHierarchy() {
489    
490            final Startable s1 = mockery.mock(Startable.class, "s1");
491            final Startable s2 = mockery.mock(Startable.class, "s2");
492            mockery.checking(new Expectations(){{
493                one(s1).start();
494                one(s1).stop();
495                one(s2).start();
496                will(throwException(new RuntimeException("I do not want to start myself")));
497             // s2 does not expect stop().
498            }});
499            
500            DefaultPicoContainer dpc = new DefaultPicoContainer();
501            dpc.addComponent("foo", s1);
502            dpc.addComponent("bar", s2);
503            dpc.addChildContainer(new DefaultPicoContainer(dpc));
504    
505            try {
506                dpc.start();
507                fail("PicoLifecylceException expected");
508            } catch (RuntimeException e) {
509                dpc.stop();
510            }
511    
512        }
513    
514        @Test public void testChildContainerIsStoppedWhenStartedIndependentlyOfParent() throws Exception {
515    
516            DefaultPicoContainer parent = new DefaultPicoContainer();
517    
518            parent.start();
519    
520            MutablePicoContainer child = parent.makeChildContainer();
521    
522            final Startable s1 = mockery.mock(Startable.class, "s1");
523            mockery.checking(new Expectations(){{
524                one(s1).start();
525                one(s1).stop();
526            }});
527            
528            child.addComponent(s1);
529    
530            child.start();
531            parent.stop();
532    
533        }
534    }