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