001    // Copyright 2006-2012 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.ioc.services;
016    
017    import org.apache.tapestry5.func.Flow;
018    import org.apache.tapestry5.ioc.*;
019    import org.apache.tapestry5.ioc.annotations.*;
020    import org.apache.tapestry5.ioc.internal.services.*;
021    import org.apache.tapestry5.ioc.internal.services.cron.PeriodicExecutorImpl;
022    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024    import org.apache.tapestry5.ioc.services.cron.PeriodicExecutor;
025    import org.apache.tapestry5.ioc.util.TimeInterval;
026    import org.apache.tapestry5.services.UpdateListenerHub;
027    
028    import java.io.File;
029    import java.lang.reflect.Array;
030    import java.math.BigDecimal;
031    import java.math.BigInteger;
032    import java.util.*;
033    import java.util.concurrent.LinkedBlockingQueue;
034    import java.util.concurrent.ThreadPoolExecutor;
035    import java.util.concurrent.TimeUnit;
036    
037    import static org.apache.tapestry5.ioc.OrderConstraintBuilder.after;
038    import static org.apache.tapestry5.ioc.OrderConstraintBuilder.before;
039    
040    /**
041     * Defines the base set of services for the Tapestry IOC container.
042     */
043    @SuppressWarnings("all")
044    @Marker(Builtin.class)
045    public final class TapestryIOCModule
046    {
047        public static void bind(ServiceBinder binder)
048        {
049            binder.bind(LoggingDecorator.class, LoggingDecoratorImpl.class);
050            binder.bind(ChainBuilder.class, ChainBuilderImpl.class);
051            binder.bind(PropertyAccess.class, PropertyAccessImpl.class);
052            binder.bind(StrategyBuilder.class, StrategyBuilderImpl.class);
053            binder.bind(PropertyShadowBuilder.class, PropertyShadowBuilderImpl.class);
054            binder.bind(PipelineBuilder.class, PipelineBuilderImpl.class).preventReloading();
055            binder.bind(DefaultImplementationBuilder.class, DefaultImplementationBuilderImpl.class);
056            binder.bind(ExceptionTracker.class, ExceptionTrackerImpl.class);
057            binder.bind(ExceptionAnalyzer.class, ExceptionAnalyzerImpl.class);
058            binder.bind(TypeCoercer.class, TypeCoercerImpl.class).preventReloading();
059            binder.bind(ThreadLocale.class, ThreadLocaleImpl.class);
060            binder.bind(SymbolSource.class, SymbolSourceImpl.class);
061            binder.bind(SymbolProvider.class, MapSymbolProvider.class).withId("ApplicationDefaults")
062                    .withMarker(ApplicationDefaults.class);
063            binder.bind(SymbolProvider.class, MapSymbolProvider.class).withId("FactoryDefaults")
064                    .withMarker(FactoryDefaults.class);
065            binder.bind(Runnable.class, RegistryStartup.class).withSimpleId();
066            binder.bind(MasterObjectProvider.class, MasterObjectProviderImpl.class).preventReloading();
067            binder.bind(ClassNameLocator.class, ClassNameLocatorImpl.class);
068            binder.bind(AspectDecorator.class, AspectDecoratorImpl.class);
069            binder.bind(ClasspathURLConverter.class, ClasspathURLConverterImpl.class);
070            binder.bind(ServiceOverride.class, ServiceOverrideImpl.class);
071            binder.bind(LoggingAdvisor.class, LoggingAdvisorImpl.class);
072            binder.bind(LazyAdvisor.class, LazyAdvisorImpl.class);
073            binder.bind(ThunkCreator.class, ThunkCreatorImpl.class);
074            binder.bind(UpdateListenerHub.class, UpdateListenerHubImpl.class).preventReloading();
075            binder.bind(PeriodicExecutor.class, PeriodicExecutorImpl.class);
076        }
077    
078        /**
079         * Provides access to additional service lifecycles. One lifecycle is built in ("singleton") but additional ones are
080         * accessed via this service (and its mapped configuration). Only proxiable services (those with explicit service
081         * interfaces) can be managed in terms of a lifecycle.
082         */
083        @PreventServiceDecoration
084        public static ServiceLifecycleSource build(Map<String, ServiceLifecycle> configuration)
085        {
086            final Map<String, ServiceLifecycle2> lifecycles = CollectionFactory.newCaseInsensitiveMap();
087    
088            for (String name : configuration.keySet())
089            {
090                lifecycles.put(name, InternalUtils.toServiceLifecycle2(configuration.get(name)));
091            }
092    
093            return new ServiceLifecycleSource()
094            {
095                public ServiceLifecycle get(String scope)
096                {
097                    return lifecycles.get(scope);
098                }
099            };
100        }
101    
102        /**
103         * Contributes the "perthread" scope.
104         */
105        @Contribute(ServiceLifecycleSource.class)
106        public static void providePerthreadScope(MappedConfiguration<String, ServiceLifecycle> configuration)
107        {
108            configuration.addInstance(ScopeConstants.PERTHREAD, PerThreadServiceLifecycle.class);
109        }
110    
111        /**
112         * <dl>
113         * <dt>AnnotationBasedContributions</dt>
114         * <dd>Empty placeholder used to separate annotation-based ObjectProvider contributions (which come before) from
115         * non-annotation based (such as ServiceOverride) which come after.</dd>
116         * <dt>Value</dt>
117         * <dd>Supports the {@link org.apache.tapestry5.ioc.annotations.Value} annotation</dd>
118         * <dt>Symbol</dt>
119         * <dd>Supports the {@link org.apache.tapestry5.ioc.annotations.Symbol} annotations</dd>
120         * <dt>Autobuild</dt>
121         * <dd>Supports the {@link org.apache.tapestry5.ioc.annotations.Autobuild} annotation</dd>
122         * <dt>ServiceOverride</dt>
123         * <dd>Allows simple service overrides via the {@link org.apache.tapestry5.ioc.services.ServiceOverride} service
124         * (and its configuration)
125         * </dl>
126         */
127        @Contribute(MasterObjectProvider.class)
128        public static void setupObjectProviders(OrderedConfiguration<ObjectProvider> configuration, @Local
129        final ServiceOverride serviceOverride)
130        {
131            configuration.add("AnnotationBasedContributions", null);
132    
133            configuration.addInstance("Value", ValueObjectProvider.class, before("AnnotationBasedContributions").build());
134            configuration.addInstance("Symbol", SymbolObjectProvider.class, before("AnnotationBasedContributions").build());
135            configuration.add("Autobuild", new AutobuildObjectProvider(), before("AnnotationBasedContributions").build());
136    
137            ObjectProvider wrapper = new ObjectProvider()
138            {
139                public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
140                {
141                    return serviceOverride.getServiceOverrideProvider().provide(objectType, annotationProvider, locator);
142                }
143            };
144    
145            configuration.add("ServiceOverride", wrapper, after("AnnotationBasedContributions").build());
146        }
147    
148        /**
149         * Contributes a set of standard type coercions to the {@link TypeCoercer} service:
150         * <ul>
151         * <li>Object to String</li>
152         * <li>Object to Boolean</li>
153         * <li>String to Double</li>
154         * <li>String to BigDecimal</li>
155         * <li>BigDecimal to Double</li>
156         * <li>Double to BigDecimal</li>
157         * <li>String to BigInteger</li>
158         * <li>BigInteger to Long</li>
159         * <li>String to Long</li>
160         * <li>Long to Byte</li>
161         * <li>Long to Short</li>
162         * <li>Long to Integer</li>
163         * <li>Double to Long</li>
164         * <li>Double to Float</li>
165         * <li>Float to Double</li>
166         * <li>Long to Double</li>
167         * <li>String to Boolean ("false" is always false, other non-blank strings are true)</li>
168         * <li>Number to Boolean (true if number value is non zero)</li>
169         * <li>Null to Boolean (always false)</li>
170         * <li>Collection to Boolean (false if empty)</li>
171         * <li>Object[] to List</li>
172         * <li>primitive[] to List</li>
173         * <li>Object to List (by wrapping as a singleton list)</li>
174         * <li>String to File</li>
175         * <li>String to {@link org.apache.tapestry5.ioc.util.TimeInterval}</li>
176         * <li>{@link org.apache.tapestry5.ioc.util.TimeInterval} to Long</li>
177         * <li>Object to Object[] (wrapping the object as an array)</li>
178         * <li>Collection to Object[] (via the toArray() method)
179         * <li>{@link Flow} to List</li>
180         * <li>{@link Flow} to Boolean (false if empty)</li>
181         * </ul>
182         */
183        @Contribute(TypeCoercer.class)
184        public static void provideBasicTypeCoercions(Configuration<CoercionTuple> configuration)
185        {
186            add(configuration, Object.class, String.class, new Coercion<Object, String>()
187            {
188                public String coerce(Object input)
189                {
190                    return input.toString();
191                }
192            });
193    
194            add(configuration, Object.class, Boolean.class, new Coercion<Object, Boolean>()
195            {
196                public Boolean coerce(Object input)
197                {
198                    return input != null;
199                }
200            });
201    
202            add(configuration, String.class, Double.class, new Coercion<String, Double>()
203            {
204                public Double coerce(String input)
205                {
206                    return new Double(input);
207                }
208            });
209    
210            // String to BigDecimal is important, as String->Double->BigDecimal would lose
211            // precision.
212    
213            add(configuration, String.class, BigDecimal.class, new Coercion<String, BigDecimal>()
214            {
215                public BigDecimal coerce(String input)
216                {
217                    return new BigDecimal(input);
218                }
219            });
220    
221            add(configuration, BigDecimal.class, Double.class, new Coercion<BigDecimal, Double>()
222            {
223                public Double coerce(BigDecimal input)
224                {
225                    return input.doubleValue();
226                }
227            });
228    
229            add(configuration, String.class, BigInteger.class, new Coercion<String, BigInteger>()
230            {
231                public BigInteger coerce(String input)
232                {
233                    return new BigInteger(input);
234                }
235            });
236    
237            add(configuration, String.class, Long.class, new Coercion<String, Long>()
238            {
239                public Long coerce(String input)
240                {
241                    return new Long(input);
242                }
243            });
244    
245            add(configuration, Long.class, Byte.class, new Coercion<Long, Byte>()
246            {
247                public Byte coerce(Long input)
248                {
249                    return input.byteValue();
250                }
251            });
252    
253            add(configuration, Long.class, Short.class, new Coercion<Long, Short>()
254            {
255                public Short coerce(Long input)
256                {
257                    return input.shortValue();
258                }
259            });
260    
261            add(configuration, Long.class, Integer.class, new Coercion<Long, Integer>()
262            {
263                public Integer coerce(Long input)
264                {
265                    return input.intValue();
266                }
267            });
268    
269            add(configuration, Number.class, Long.class, new Coercion<Number, Long>()
270            {
271                public Long coerce(Number input)
272                {
273                    return input.longValue();
274                }
275            });
276    
277            add(configuration, Double.class, Float.class, new Coercion<Double, Float>()
278            {
279                public Float coerce(Double input)
280                {
281                    return input.floatValue();
282                }
283            });
284    
285            add(configuration, Long.class, Double.class, new Coercion<Long, Double>()
286            {
287                public Double coerce(Long input)
288                {
289                    return input.doubleValue();
290                }
291            });
292    
293            add(configuration, String.class, Boolean.class, new Coercion<String, Boolean>()
294            {
295                public Boolean coerce(String input)
296                {
297                    String trimmed = input == null ? "" : input.trim();
298    
299                    if (trimmed.equalsIgnoreCase("false") || trimmed.length() == 0)
300                        return false;
301    
302                    // Any non-blank string but "false"
303    
304                    return true;
305                }
306            });
307    
308            add(configuration, Number.class, Boolean.class, new Coercion<Number, Boolean>()
309            {
310                public Boolean coerce(Number input)
311                {
312                    return input.longValue() != 0;
313                }
314            });
315    
316            add(configuration, Void.class, Boolean.class, new Coercion<Void, Boolean>()
317            {
318                public Boolean coerce(Void input)
319                {
320                    return false;
321                }
322            });
323    
324            add(configuration, Collection.class, Boolean.class, new Coercion<Collection, Boolean>()
325            {
326                public Boolean coerce(Collection input)
327                {
328                    return !input.isEmpty();
329                }
330            });
331    
332            add(configuration, Object.class, List.class, new Coercion<Object, List>()
333            {
334                public List coerce(Object input)
335                {
336                    return Collections.singletonList(input);
337                }
338            });
339    
340            add(configuration, Object[].class, List.class, new Coercion<Object[], List>()
341            {
342                public List coerce(Object[] input)
343                {
344                    return Arrays.asList(input);
345                }
346            });
347    
348            add(configuration, Object[].class, Boolean.class, new Coercion<Object[], Boolean>()
349            {
350                public Boolean coerce(Object[] input)
351                {
352                    return input != null && input.length > 0;
353                }
354            });
355    
356            add(configuration, Float.class, Double.class, new Coercion<Float, Double>()
357            {
358                public Double coerce(Float input)
359                {
360                    return input.doubleValue();
361                }
362            });
363    
364            Coercion primitiveArrayCoercion = new Coercion<Object, List>()
365            {
366                public List<Object> coerce(Object input)
367                {
368                    int length = Array.getLength(input);
369                    Object[] array = new Object[length];
370                    for (int i = 0; i < length; i++)
371                    {
372                        array[i] = Array.get(input, i);
373                    }
374                    return Arrays.asList(array);
375                }
376            };
377    
378            add(configuration, byte[].class, List.class, primitiveArrayCoercion);
379            add(configuration, short[].class, List.class, primitiveArrayCoercion);
380            add(configuration, int[].class, List.class, primitiveArrayCoercion);
381            add(configuration, long[].class, List.class, primitiveArrayCoercion);
382            add(configuration, float[].class, List.class, primitiveArrayCoercion);
383            add(configuration, double[].class, List.class, primitiveArrayCoercion);
384            add(configuration, char[].class, List.class, primitiveArrayCoercion);
385            add(configuration, boolean[].class, List.class, primitiveArrayCoercion);
386    
387            add(configuration, String.class, File.class, new Coercion<String, File>()
388            {
389                public File coerce(String input)
390                {
391                    return new File(input);
392                }
393            });
394    
395            add(configuration, String.class, TimeInterval.class, new Coercion<String, TimeInterval>()
396            {
397                public TimeInterval coerce(String input)
398                {
399                    return new TimeInterval(input);
400                }
401            });
402    
403            add(configuration, TimeInterval.class, Long.class, new Coercion<TimeInterval, Long>()
404            {
405                public Long coerce(TimeInterval input)
406                {
407                    return input.milliseconds();
408                }
409            });
410    
411            add(configuration, Object.class, Object[].class, new Coercion<Object, Object[]>()
412            {
413                public Object[] coerce(Object input)
414                {
415                    return new Object[]
416                            {input};
417                }
418            });
419    
420            add(configuration, Collection.class, Object[].class, new Coercion<Collection, Object[]>()
421            {
422                public Object[] coerce(Collection input)
423                {
424                    return input.toArray();
425                }
426            });
427    
428            add(configuration, Flow.class, List.class, new Coercion<Flow, List>()
429            {
430                public List coerce(Flow input)
431                {
432                    return input.toList();
433                }
434            });
435    
436            add(configuration, Flow.class, Boolean.class, new Coercion<Flow, Boolean>()
437            {
438                public Boolean coerce(Flow input)
439                {
440                    return !input.isEmpty();
441                }
442            });
443    
444        }
445    
446        private static <S, T> void add(Configuration<CoercionTuple> configuration, Class<S> sourceType,
447                                       Class<T> targetType, Coercion<S, T> coercion)
448        {
449            CoercionTuple<S, T> tuple = new CoercionTuple<S, T>(sourceType, targetType, coercion);
450    
451            configuration.add(tuple);
452        }
453    
454        /**
455         * <dl>
456         * <dt>SystemProperties</dt>
457         * <dd>Exposes JVM System properties as symbols (currently case-sensitive)</dd>
458         * <dt>EnvironmentVariables</dt>
459         * <dd>Exposes environment variables as symbols (adding a "env." prefix)</dd>
460         * <dt>ApplicationDefaults</dt>
461         * <dd>Values contributed to @{@link SymbolProvider} @{@link ApplicationDefaults}</dd>
462         * <dt>FactoryDefaults</dt>
463         * <dd>Values contributed to @{@link SymbolProvider} @{@link FactoryDefaults}</dd>
464         * </dl>
465         */
466        @Contribute(SymbolSource.class)
467        public static void setupStandardSymbolProviders(OrderedConfiguration<SymbolProvider> configuration,
468                                                        @ApplicationDefaults
469                                                        SymbolProvider applicationDefaults,
470    
471                                                        @FactoryDefaults
472                                                        SymbolProvider factoryDefaults)
473        {
474            configuration.add("SystemProperties", new SystemPropertiesSymbolProvider(), "before:*");
475            configuration.add("EnvironmentVariables", new SystemEnvSymbolProvider());
476            configuration.add("ApplicationDefaults", applicationDefaults);
477            configuration.add("FactoryDefaults", factoryDefaults);
478        }
479    
480        public static ParallelExecutor buildDeferredExecution(@Symbol(IOCSymbols.THREAD_POOL_CORE_SIZE)
481                                                              int coreSize,
482    
483                                                              @Symbol(IOCSymbols.THREAD_POOL_MAX_SIZE)
484                                                              int maxSize,
485    
486                                                              @Symbol(IOCSymbols.THREAD_POOL_KEEP_ALIVE)
487                                                              @IntermediateType(TimeInterval.class)
488                                                              int keepAliveMillis,
489    
490                                                              @Symbol(IOCSymbols.THREAD_POOL_ENABLED)
491                                                              boolean threadPoolEnabled,
492    
493                                                              @Symbol(IOCSymbols.THREAD_POOL_QUEUE_SIZE)
494                                                              int queueSize,
495    
496                                                              PerthreadManager perthreadManager,
497    
498                                                              RegistryShutdownHub shutdownHub,
499    
500                                                              ThunkCreator thunkCreator)
501        {
502    
503            if (!threadPoolEnabled)
504                return new NonParallelExecutor();
505    
506            LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(queueSize);
507    
508            final ThreadPoolExecutor executorService = new ThreadPoolExecutor(coreSize, maxSize, keepAliveMillis,
509                    TimeUnit.MILLISECONDS, workQueue);
510    
511            shutdownHub.addRegistryShutdownListener(new Runnable()
512            {
513                public void run()
514                {
515                    executorService.shutdown();
516                }
517            });
518    
519            return new ParallelExecutorImpl(executorService, thunkCreator, perthreadManager);
520        }
521    
522        @Contribute(SymbolProvider.class)
523        @FactoryDefaults
524        public static void setupDefaultSymbols(MappedConfiguration<String, Object> configuration)
525        {
526            configuration.add(IOCSymbols.THREAD_POOL_CORE_SIZE, 3);
527            configuration.add(IOCSymbols.THREAD_POOL_MAX_SIZE, 20);
528            configuration.add(IOCSymbols.THREAD_POOL_KEEP_ALIVE, "1 m");
529            configuration.add(IOCSymbols.THREAD_POOL_ENABLED, true);
530            configuration.add(IOCSymbols.THREAD_POOL_QUEUE_SIZE, 100);
531        }
532    }