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.txt file.                                                     *
007     *                                                                           *
008     *****************************************************************************/
009    package org.picocontainer.injectors;
010    
011    import org.junit.Test;
012    import org.picocontainer.ComponentMonitor;
013    import org.picocontainer.DefaultPicoContainer;
014    import org.picocontainer.LifecycleStrategy;
015    import org.picocontainer.MutablePicoContainer;
016    import org.picocontainer.PicoCompositionException;
017    import org.picocontainer.annotations.Nullable;
018    import org.picocontainer.behaviors.Caching;
019    import org.picocontainer.behaviors.ThreadCaching;
020    import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
021    import org.picocontainer.monitors.LifecycleComponentMonitor;
022    
023    import static org.junit.Assert.assertEquals;
024    import static org.junit.Assert.assertNotNull;
025    import static org.junit.Assert.assertTrue;
026    import static org.junit.Assert.fail;
027    
028    public class ProviderTestCase {
029        
030        @Test
031        public void provideMethodCanParticipateInInjection() {
032            DefaultPicoContainer dpc = new DefaultPicoContainer();
033            dpc.addAdapter(new Chocolatier(true));
034            dpc.addComponent(NeedsChocolate.class);
035            dpc.addComponent(CocaoBeans.class);
036            dpc.addComponent(String.class, "Cadbury's"); // the only string in the set of components
037            NeedsChocolate needsChocolate = dpc.getComponent(NeedsChocolate.class);
038            assertNotNull(needsChocolate);
039            assertNotNull(needsChocolate.choc);
040            assertEquals(true, needsChocolate.choc.milky);
041            assertNotNull(needsChocolate.choc.cocaoBeans);
042            assertEquals("Cadbury's", needsChocolate.choc.name);
043        }
044    
045        @Test
046        public void provideMethodCanDisambiguateUsingParameterNames() {
047            DefaultPicoContainer dpc = new DefaultPicoContainer();
048            dpc.addAdapter(new Chocolatier(true));
049            dpc.addComponent(NeedsChocolate.class);
050            dpc.addComponent(CocaoBeans.class);
051            dpc.addComponent("color", "Red"); // not used by virtue of key
052            dpc.addComponent("name", "Cadbury's");
053            dpc.addComponent("band", "Abba"); // not used by virtue of key
054            NeedsChocolate needsChocolate = dpc.getComponent(NeedsChocolate.class);
055            assertNotNull(needsChocolate);
056            assertNotNull(needsChocolate.choc);
057            assertEquals(true, needsChocolate.choc.milky);
058            assertNotNull(needsChocolate.choc.cocaoBeans);
059            assertEquals("Cadbury's", needsChocolate.choc.name);
060        }
061    
062        @Test
063        public void providerBarfsIfProvideMethodsParamsCanNotBeSatisfied() {
064            DefaultPicoContainer dpc = new DefaultPicoContainer();
065            dpc.addAdapter(new Chocolatier(true));
066            dpc.addComponent(NeedsChocolate.class);
067            try {
068                dpc.getComponent(NeedsChocolate.class);
069            } catch (PicoCompositionException e) {
070                assertTrue(e.getMessage().contains("Parameter 0 "));
071                assertTrue(e.getMessage().contains("cannot be null"));
072            }
073        }
074    
075        @Test
076        public void providerDoesNotBarfIfProvideMethodsParamsCanNotBeSatisfiedButNullbleAnnotationUsed() {
077            DefaultPicoContainer dpc = new DefaultPicoContainer();
078            dpc.addAdapter(new NullableChocolatier());
079            dpc.addComponent(NeedsChocolate.class);
080            NeedsChocolate nc = dpc.getComponent(NeedsChocolate.class);
081            assertNotNull(nc);
082            assertNotNull(nc.choc);
083            assertTrue(nc.choc.cocaoBeans == null);
084        }
085    
086        @Test
087        public void testHasLifecycle() {
088            DefaultPicoContainer dpc = new DefaultPicoContainer(new Caching());
089            dpc.addAdapter(new NullableChocolatier());
090            dpc.addComponent(NeedsChocolate.class);
091            NeedsChocolate nc = dpc.getComponent(NeedsChocolate.class);
092            dpc.start();
093            dpc.stop();
094            dpc.dispose();
095        }
096    
097        public static class CocaoBeans {
098        }
099    
100        public static class Chocolate {
101            private boolean milky;
102            private final CocaoBeans cocaoBeans;
103            private final String name;
104    
105            public Chocolate(String name) {
106                this(true, new CocaoBeans(), name);
107            }
108    
109            public Chocolate(boolean milky, CocaoBeans cocaoBeans, String name) {
110                this.milky = milky;
111                this.cocaoBeans = cocaoBeans;
112                this.name = name;
113            }
114        }
115    
116        public static class Chocolatier extends ProviderAdapter {
117            private final boolean milky;
118            public Chocolatier(boolean milky) {
119                this.milky = milky;
120            }
121            public Chocolate provide(CocaoBeans cocaoBeans, String name) {
122                return new Chocolate(milky, cocaoBeans, name);
123            }
124            @Override
125            protected boolean useNames() {
126                return true;
127            }
128        }
129    
130        public static class NullableChocolatier extends Chocolatier {
131            public NullableChocolatier() {
132                super(true);
133            }
134    
135            public Chocolate provide(@Nullable CocaoBeans cocaoBeans, @Nullable String name) {
136                return super.provide(cocaoBeans, name);
137            }
138        }
139    
140        public static class NeedsChocolate {
141            private Chocolate choc;
142            public NeedsChocolate(Chocolate choc) {
143                this.choc = choc;
144            }
145        }
146    
147        @Test
148        public void providerBarfsIfNoProvideMethod() {
149            DefaultPicoContainer dpc = new DefaultPicoContainer();
150            try {
151                dpc.addAdapter(new ProviderWithoutProvideMethod());
152                fail("should have barfed");
153            } catch (PicoCompositionException e) {
154                assertEquals("There must be a method named 'provide' in the AbstractProvider implementation", e.getMessage());
155            }
156        }
157    
158        @Test
159        public void providerBarfsIfBadProvideMethod() {
160            DefaultPicoContainer dpc = new DefaultPicoContainer();
161            try {
162                dpc.addAdapter(new ProviderWithBadProvideMethod());
163                fail("should have barfed");
164            } catch (PicoCompositionException e) {
165                assertEquals("There must be a non void returning method named 'provide' in the AbstractProvider implementation", e.getMessage());
166            }
167        }
168    
169        @Test
170        public void providerBarfsIfTooManyProvideMethod() {
171            DefaultPicoContainer dpc = new DefaultPicoContainer();
172            try {
173                dpc.addAdapter(new ProviderWithTooManyProvideMethods());
174                fail("should have barfed");
175            } catch (PicoCompositionException e) {
176                assertEquals("There must be only one method named 'provide' in the AbstractProvider implementation", e.getMessage());
177            }
178        }
179    
180        public static class ProviderWithoutProvideMethod extends ProviderAdapter {
181        }
182        public static class ProviderWithBadProvideMethod extends ProviderAdapter {
183            public void provide() {
184    
185            }
186        }
187        public static class ProviderWithTooManyProvideMethods extends ProviderAdapter {
188            public String provide(String str) {
189                return null;
190            }
191            public Integer provide() {
192                return null;
193            }
194        }
195    
196        @Test
197        public void provideMethodCanParticipateInInjectionWhenNotExtendingProviderAdapter() {
198            DefaultPicoContainer dpc = new DefaultPicoContainer();
199            dpc.addAdapter(new ProviderAdapter(new Chocolatier2(true)));
200            dpc.addComponent(NeedsChocolate.class);
201            dpc.addComponent(CocaoBeans.class);
202            dpc.addComponent(String.class, "Cadbury's"); // the only string in the set of components
203            NeedsChocolate needsChocolate = dpc.getComponent(NeedsChocolate.class);
204            assertNotNull(needsChocolate);
205            assertNotNull(needsChocolate.choc);
206            assertEquals(true, needsChocolate.choc.milky);
207            assertNotNull(needsChocolate.choc.cocaoBeans);
208            assertEquals("Cadbury's", needsChocolate.choc.name);
209        }
210    
211        public static class Chocolatier2 implements Provider {
212            private final boolean milky;
213            public Chocolatier2(boolean milky) {
214                this.milky = milky;
215            }
216            public Chocolate provide(CocaoBeans cocaoBeans, String name) {
217                return new Chocolate(milky, cocaoBeans, name);
218            }
219        }
220    
221        @Test
222        public void providedTypeCanBeDyanamicallyDeterminedFromInstanceRatherThanType() {
223            DefaultPicoContainer dpc = new DefaultPicoContainer();
224    
225            // a simlation of what a web framework would essentially do in a thread-local way
226            dpc.addComponent(new StubHttpRequest("chocolate", "Lindt"));
227    
228            // this is the style being recomended for automatic request-params -> beans
229            dpc.addAdapter(new ExampleRequestReader(Chocolate.class, "chocolate"));
230    
231            dpc.addComponent(NeedsChocolate.class);
232            NeedsChocolate needsChocolate = dpc.getComponent(NeedsChocolate.class);
233            assertNotNull(needsChocolate);
234            assertNotNull(needsChocolate.choc);
235            assertEquals(true, needsChocolate.choc.milky);
236            assertNotNull(needsChocolate.choc.cocaoBeans);
237            assertEquals("Lindt", needsChocolate.choc.name);
238        }
239    
240    
241        public static class StubHttpRequest {
242            private final String key;
243            private final String value;
244    
245            public StubHttpRequest(String key, String value) {
246                this.key = key;
247                this.value = value;
248            }
249    
250            public String getParameter(String name) {
251                return name.equals(key) ? value : null;
252            }
253        }
254    
255        public static class ExampleRequestReader extends ProviderAdapter {
256            private final Class clazz;
257            private final String paramName;
258    
259            public ExampleRequestReader(Class clazz, String paramName) {
260                this.clazz = clazz;
261                this.paramName = paramName;
262            }
263    
264            public Class getComponentImplementation() {
265                return clazz;
266            }
267    
268            public Object provide(StubHttpRequest req) {
269                try {
270                    return clazz.getConstructor(String.class).newInstance(req.getParameter(paramName));
271                } catch (Exception e) {
272                    throw new RuntimeException(e);  
273                }
274            }
275        }
276    
277        @Test
278        public void providersCanHaveLifecyclesToo() {
279            ComponentMonitor componentMonitor = new LifecycleComponentMonitor();
280            LifecycleStrategy lifecycleStrategy = new
281                    ReflectionLifecycleStrategy(componentMonitor);
282    
283            MutablePicoContainer pico = new DefaultPicoContainer(new
284                    ThreadCaching(), lifecycleStrategy, null);
285    
286            StringBuilder sb = new StringBuilder();
287            pico.addComponent(Configuration.class);
288            pico.addAdapter(new ProviderAdapter(lifecycleStrategy, new ComponentProvider(sb)));
289            Object foo = pico.getComponent(Component.class);
290            pico.start();
291            pico.stop();
292            assertEquals("@<>", sb.toString());
293    
294        }
295    
296        public class ComponentProvider implements Provider {
297            private StringBuilder sb;
298    
299            public ComponentProvider(StringBuilder sb) {
300                this.sb = sb;
301            }
302    
303            public Component provide(Configuration config) {
304                return new ComponentImpl(sb, config.getHost(), config.getPort());
305            }
306        }
307    
308        public static class Configuration {
309    
310            public String getHost() {
311                return "hello";
312            }
313    
314            public int getPort() {
315                return 99;
316            }
317    
318            public void start() {
319            }
320    
321            public void stop() {
322            }
323    
324        }
325    
326        public static interface Component {
327    
328            public void start();
329    
330            public void stop();
331    
332        }
333    
334        public static class ComponentImpl implements Component {
335    
336            private StringBuilder sb;
337    
338            public ComponentImpl(StringBuilder sb, String host, int port) {
339                this.sb = sb.append("@");
340            }
341    
342            public void start() {
343                sb.append("<");
344            }
345            public void stop() {
346                sb.append(">");
347            }
348    
349        }
350    
351    
352    }