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 }