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            this.resources = resources;
060            this.creatorDescription = creatorDescription;
061            logger = resources.getLogger();
062    
063            injectionResources.put(String.class, serviceId);
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 parameterDefaults) 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        private List getOrderedConfiguration(Type genericType)
119        {
120            Class valueType = findParameterizedTypeFromGenericType(genericType);
121            return resources.getOrderedConfiguration(valueType);
122        }
123    
124    
125        @SuppressWarnings("unchecked")
126        private Collection getUnorderedConfiguration(Type genericType)
127        {
128            Class valueType = findParameterizedTypeFromGenericType(genericType);
129    
130            return resources.getUnorderedConfiguration(valueType);
131        }
132    
133        @SuppressWarnings("unchecked")
134        private Map getMappedConfiguration(Type genericType)
135        {
136            Class keyType = findParameterizedTypeFromGenericType(genericType, 0);
137            Class valueType = findParameterizedTypeFromGenericType(genericType, 1);
138    
139            if (keyType == null || valueType == null)
140                throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(genericType));
141    
142            return resources.getMappedConfiguration(keyType, valueType);
143        }
144    
145        /**
146         * Extracts from a generic type the underlying parameterized type. I.e., for List<Runnable>, will return Runnable.
147         * This is limited to simple parameterized types, not the more complex cases involving wildcards and upper/lower
148         * boundaries.
149         *
150         * @param type the genetic type of the parameter, i.e., List<Runnable>
151         * @return the parameterize type (i.e. Runnable.class if type represents List<Runnable>).
152         */
153    
154        // package private for testing
155        static Class findParameterizedTypeFromGenericType(Type type)
156        {
157            Class result = findParameterizedTypeFromGenericType(type, 0);
158    
159            if (result == null) throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(type));
160    
161            return result;
162        }
163    
164        /**
165         * "Sniffs" a generic type to find the underlying parameterized type. If the Type is a class, then Object.class is
166         * returned. Otherwise, the type must be a ParameterizedType. We check to make sure it has the correct number of a
167         * actual types (1 for a Collection or List, 2 for a Map). The actual types must be classes (wildcards just aren't
168         * supported)
169         *
170         * @param type      a Class or ParameterizedType to inspect
171         * @param typeIndex the index within the ParameterizedType to extract
172         * @return the actual type, or Object.class if the input type is not generic, or null if any other pre-condition is
173         *         not met
174         */
175        private static Class findParameterizedTypeFromGenericType(Type type, int typeIndex)
176        {
177            // For a raw Class type, it means the parameter is not parameterized (i.e. Collection, not
178            // Collection<Foo>), so we can return Object.class to allow no restriction.
179    
180            if (type instanceof Class) return Object.class;
181    
182            if (!(type instanceof ParameterizedType)) return null;
183    
184            ParameterizedType pt = (ParameterizedType) type;
185    
186            Type[] types = pt.getActualTypeArguments();
187    
188            Type actualType = types[typeIndex];
189    
190            return actualType instanceof Class ? (Class) actualType : null;
191        }
192    }