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 }