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