001    // Copyright 2007, 2008 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.internal;
016    
017    import org.apache.tapestry5.ioc.*;
018    import static org.apache.tapestry5.ioc.internal.ConfigurationType.*;
019    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020    import org.apache.tapestry5.ioc.internal.util.DelegatingInjectionResources;
021    import org.apache.tapestry5.ioc.internal.util.InjectionResources;
022    import org.apache.tapestry5.ioc.internal.util.MapInjectionResources;
023    import org.slf4j.Logger;
024    
025    import java.lang.reflect.ParameterizedType;
026    import java.lang.reflect.Type;
027    import java.util.Collection;
028    import java.util.List;
029    import java.util.Map;
030    
031    /**
032     * Abstract implementation of {@link ObjectCreator} geared towards the creation of the core service implementation,
033     * either by invoking a service builder method on a module, or by invoking a constructor.
034     */
035    public abstract class AbstractServiceCreator implements ObjectCreator
036    {
037        protected final String serviceId;
038    
039        private final Map<Class, Object> injectionResources = CollectionFactory.newMap();
040    
041        protected final ServiceBuilderResources resources;
042    
043        protected final Logger logger;
044    
045        private final static Map<Class, ConfigurationType> PARAMETER_TYPE_TO_CONFIGURATION_TYPE = CollectionFactory.newMap();
046    
047        protected final String creatorDescription;
048    
049        static
050        {
051            PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Collection.class, UNORDERED);
052            PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(List.class, ORDERED);
053            PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Map.class, MAPPED);
054        }
055    
056        public AbstractServiceCreator(ServiceBuilderResources resources, String creatorDescription)
057        {
058            serviceId = resources.getServiceId();
059    
060            this.resources = resources;
061            this.creatorDescription = creatorDescription;
062            logger = resources.getLogger();
063    
064            injectionResources.put(ObjectLocator.class, resources);
065            injectionResources.put(ServiceResources.class, resources);
066            injectionResources.put(Logger.class, logger);
067            injectionResources.put(Class.class, resources.getServiceInterface());
068            injectionResources.put(OperationTracker.class, resources.getTracker());
069        }
070    
071        /**
072         * Returns a map (based on injectionResources) that includes (possibly) an additional mapping containing the
073         * collected configuration data. This involves scanning the parameters and generic types.
074         */
075        protected final InjectionResources createInjectionResources()
076        {
077            InjectionResources core = new MapInjectionResources(injectionResources);
078    
079            InjectionResources configurations = new InjectionResources()
080            {
081                private boolean seenOne;
082    
083                public <T> T findResource(Class<T> resourceType, Type genericType)
084                {
085                    ConfigurationType thisType = PARAMETER_TYPE_TO_CONFIGURATION_TYPE.get(resourceType);
086    
087                    if (thisType == null) return null;
088    
089                    if (seenOne)
090                        throw new RuntimeException(IOCMessages.tooManyConfigurationParameters(creatorDescription));
091    
092    
093                    seenOne = true;
094    
095                    switch (thisType)
096                    {
097                        case UNORDERED:
098    
099                            return resourceType.cast(getUnorderedConfiguration(genericType));
100    
101                        case ORDERED:
102    
103                            return resourceType.cast(getOrderedConfiguration(genericType));
104    
105                        case MAPPED:
106    
107                            return resourceType.cast(getMappedConfiguration(genericType));
108                    }
109    
110                    return null;
111                }
112            };
113    
114    
115            return new DelegatingInjectionResources(core, configurations);
116        }
117    
118        @SuppressWarnings("unchecked")
119        private List getOrderedConfiguration(Type genericType)
120        {
121            Class valueType = findParameterizedTypeFromGenericType(genericType);
122            
123            return resources.getOrderedConfiguration(valueType);
124        }
125    
126    
127        @SuppressWarnings("unchecked")
128        private Collection getUnorderedConfiguration(Type genericType)
129        {
130            Class valueType = findParameterizedTypeFromGenericType(genericType);
131    
132            return resources.getUnorderedConfiguration(valueType);
133        }
134    
135        @SuppressWarnings("unchecked")
136        private Map getMappedConfiguration(Type genericType)
137        {
138            Class keyType = findParameterizedTypeFromGenericType(genericType, 0);
139            Class valueType = findParameterizedTypeFromGenericType(genericType, 1);
140    
141            if (keyType == null || valueType == null)
142                throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(genericType));
143    
144            return resources.getMappedConfiguration(keyType, valueType);
145        }
146    
147        /**
148         * Extracts from a generic type the underlying parameterized type. I.e., for List<Runnable>, will return Runnable.
149         * This is limited to simple parameterized types, not the more complex cases involving wildcards and upper/lower
150         * boundaries.
151         *
152         * @param type the genetic type of the parameter, i.e., List<Runnable>
153         * @return the parameterize type (i.e. Runnable.class if type represents List<Runnable>).
154         */
155    
156        // package private for testing
157        static Class findParameterizedTypeFromGenericType(Type type)
158        {
159            Class result = findParameterizedTypeFromGenericType(type, 0);
160    
161            if (result == null) throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(type));
162    
163            return result;
164        }
165    
166        /**
167         * "Sniffs" a generic type to find the underlying parameterized type. If the Type is a class, then Object.class is
168         * returned. Otherwise, the type must be a ParameterizedType. We check to make sure it has the correct number of a
169         * actual types (1 for a Collection or List, 2 for a Map). The actual types must be classes (wildcards just aren't
170         * supported)
171         *
172         * @param type      a Class or ParameterizedType to inspect
173         * @param typeIndex the index within the ParameterizedType to extract
174         * @return the actual type, or Object.class if the input type is not generic, or null if any other pre-condition is
175         *         not met
176         */
177        private static Class findParameterizedTypeFromGenericType(Type type, int typeIndex)
178        {
179            // For a raw Class type, it means the parameter is not parameterized (i.e. Collection, not
180            // Collection<Foo>), so we can return Object.class to allow no restriction.
181    
182            if (type instanceof Class) return Object.class;
183    
184            if (!(type instanceof ParameterizedType)) return null;
185    
186            ParameterizedType pt = (ParameterizedType) type;
187    
188            Type[] types = pt.getActualTypeArguments();
189    
190            Type actualType = types[typeIndex];
191    
192            return actualType instanceof Class ? (Class) actualType : null;
193        }
194    }