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 015package org.apache.tapestry5.ioc.internal; 016 017import org.apache.tapestry5.ioc.*; 018import static org.apache.tapestry5.ioc.internal.ConfigurationType.*; 019import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 020import org.apache.tapestry5.ioc.internal.util.DelegatingInjectionResources; 021import org.apache.tapestry5.ioc.internal.util.InjectionResources; 022import org.apache.tapestry5.ioc.internal.util.MapInjectionResources; 023import org.slf4j.Logger; 024 025import java.lang.reflect.ParameterizedType; 026import java.lang.reflect.Type; 027import java.util.Collection; 028import java.util.List; 029import 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 */ 035public 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 @Override 084 public <T> T findResource(Class<T> resourceType, Type genericType) 085 { 086 ConfigurationType thisType = PARAMETER_TYPE_TO_CONFIGURATION_TYPE.get(resourceType); 087 088 if (thisType == null) return null; 089 090 if (seenOne) 091 throw new RuntimeException(IOCMessages.tooManyConfigurationParameters(creatorDescription)); 092 093 094 seenOne = true; 095 096 switch (thisType) 097 { 098 case UNORDERED: 099 100 return resourceType.cast(getUnorderedConfiguration(genericType)); 101 102 case ORDERED: 103 104 return resourceType.cast(getOrderedConfiguration(genericType)); 105 106 case MAPPED: 107 108 return resourceType.cast(getMappedConfiguration(genericType)); 109 } 110 111 return null; 112 } 113 }; 114 115 116 return new DelegatingInjectionResources(core, configurations); 117 } 118 119 @SuppressWarnings("unchecked") 120 private List getOrderedConfiguration(Type genericType) 121 { 122 Class valueType = findParameterizedTypeFromGenericType(genericType); 123 124 return resources.getOrderedConfiguration(valueType); 125 } 126 127 128 @SuppressWarnings("unchecked") 129 private Collection getUnorderedConfiguration(Type genericType) 130 { 131 Class valueType = findParameterizedTypeFromGenericType(genericType); 132 133 return resources.getUnorderedConfiguration(valueType); 134 } 135 136 @SuppressWarnings("unchecked") 137 private Map getMappedConfiguration(Type genericType) 138 { 139 Class keyType = findParameterizedTypeFromGenericType(genericType, 0); 140 Class valueType = findParameterizedTypeFromGenericType(genericType, 1); 141 142 if (keyType == null || valueType == null) 143 throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(genericType)); 144 145 return resources.getMappedConfiguration(keyType, valueType); 146 } 147 148 /** 149 * Extracts from a generic type the underlying parameterized type. I.e., for List<Runnable>, will return Runnable. 150 * This is limited to simple parameterized types, not the more complex cases involving wildcards and upper/lower 151 * boundaries. 152 * 153 * @param type the genetic type of the parameter, i.e., List<Runnable> 154 * @return the parameterize type (i.e. Runnable.class if type represents List<Runnable>). 155 */ 156 157 // package private for testing 158 static Class findParameterizedTypeFromGenericType(Type type) 159 { 160 Class result = findParameterizedTypeFromGenericType(type, 0); 161 162 if (result == null) throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(type)); 163 164 return result; 165 } 166 167 /** 168 * "Sniffs" a generic type to find the underlying parameterized type. If the Type is a class, then Object.class is 169 * returned. Otherwise, the type must be a ParameterizedType. We check to make sure it has the correct number of a 170 * actual types (1 for a Collection or List, 2 for a Map). The actual types must be classes (wildcards just aren't 171 * supported) 172 * 173 * @param type a Class or ParameterizedType to inspect 174 * @param typeIndex the index within the ParameterizedType to extract 175 * @return the actual type, or Object.class if the input type is not generic, or null if any other pre-condition is 176 * not met 177 */ 178 private static Class findParameterizedTypeFromGenericType(Type type, int typeIndex) 179 { 180 // For a raw Class type, it means the parameter is not parameterized (i.e. Collection, not 181 // Collection<Foo>), so we can return Object.class to allow no restriction. 182 183 if (type instanceof Class) return Object.class; 184 185 if (!(type instanceof ParameterizedType)) return null; 186 187 ParameterizedType pt = (ParameterizedType) type; 188 189 Type[] types = pt.getActualTypeArguments(); 190 191 Type actualType = types[typeIndex]; 192 193 return actualType instanceof Class ? (Class) actualType : null; 194 } 195}