    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    }