    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&oslash;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    }