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