    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 static org.apache.tapestry5.ioc.internal.ConfigurationType.*;
    018
    019import org.apache.tapestry5.commons.*;
    020import org.apache.tapestry5.commons.util.CollectionFactory;
    021import org.apache.tapestry5.ioc.OperationTracker;
    022import org.apache.tapestry5.ioc.ServiceBuilderResources;
    023import org.apache.tapestry5.ioc.ServiceResources;
    024import org.apache.tapestry5.ioc.internal.util.DelegatingInjectionResources;
    025import org.apache.tapestry5.ioc.internal.util.InjectionResources;
    026import org.apache.tapestry5.ioc.internal.util.MapInjectionResources;
    027import org.slf4j.Logger;
    028
    029import java.lang.reflect.ParameterizedType;
    030import java.lang.reflect.Type;
    031import java.util.Collection;
    032import java.util.List;
    033import java.util.Map;
    034
    035/**
    036 * Abstract implementation of {@link ObjectCreator} geared towards the creation of the core service implementation,
    037 * either by invoking a service builder method on a module, or by invoking a constructor.
    038 */
    039public abstract class AbstractServiceCreator implements ObjectCreator
    040{
    041    protected final String serviceId;
    042
    043    private final Map<Class, Object> injectionResources = CollectionFactory.newMap();
    044
    045    protected final ServiceBuilderResources resources;
    046
    047    protected final Logger logger;
    048
    049    private final static Map<Class, ConfigurationType> PARAMETER_TYPE_TO_CONFIGURATION_TYPE = CollectionFactory.newMap();
    050
    051    protected final String creatorDescription;
    052
    053    static
    054    {
    055        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Collection.class, UNORDERED);
    056        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(List.class, ORDERED);
    057        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Map.class, MAPPED);
    058    }
    059
    060    public AbstractServiceCreator(ServiceBuilderResources resources, String creatorDescription)
    061    {
    062        serviceId = resources.getServiceId();
    063
    064        this.resources = resources;
    065        this.creatorDescription = creatorDescription;
    066        logger = resources.getLogger();
    067
    068        injectionResources.put(ObjectLocator.class, resources);
    069        injectionResources.put(ServiceResources.class, resources);
    070        injectionResources.put(Logger.class, logger);
    071        injectionResources.put(Class.class, resources.getServiceInterface());
    072        injectionResources.put(OperationTracker.class, resources.getTracker());
    073    }
    074
    075    /**
    076     * Returns a map (based on injectionResources) that includes (possibly) an additional mapping containing the
    077     * collected configuration data. This involves scanning the parameters and generic types.
    078     */
    079    protected final InjectionResources createInjectionResources()
    080    {
    081        InjectionResources core = new MapInjectionResources(injectionResources);
    082
    083        InjectionResources configurations = new InjectionResources()
    084        {
    085            private boolean seenOne;
    086
    087            @Override
    088            public <T> T findResource(Class<T> resourceType, Type genericType)
    089            {
    090                ConfigurationType thisType = PARAMETER_TYPE_TO_CONFIGURATION_TYPE.get(resourceType);
    091
    092                if (thisType == null) return null;
    093
    094                if (seenOne)
    095                    throw new RuntimeException(IOCMessages.tooManyConfigurationParameters(creatorDescription));
    096
    097
    098                seenOne = true;
    099
    100                switch (thisType)
    101                {
    102                    case UNORDERED:
    103
    104                        return resourceType.cast(getUnorderedConfiguration(genericType));
    105
    106                    case ORDERED:
    107
    108                        return resourceType.cast(getOrderedConfiguration(genericType));
    109
    110                    case MAPPED:
    111
    112                        return resourceType.cast(getMappedConfiguration(genericType));
    113                }
    114
    115                return null;
    116            }
    117        };
    118
    119
    120        return new DelegatingInjectionResources(core, configurations);
    121    }
    122
    123    @SuppressWarnings("unchecked")
    124    private List getOrderedConfiguration(Type genericType)
    125    {
    126        Class valueType = findParameterizedTypeFromGenericType(genericType);
    127
    128        return resources.getOrderedConfiguration(valueType);
    129    }
    130
    131
    132    @SuppressWarnings("unchecked")
    133    private Collection getUnorderedConfiguration(Type genericType)
    134    {
    135        Class valueType = findParameterizedTypeFromGenericType(genericType);
    136
    137        return resources.getUnorderedConfiguration(valueType);
    138    }
    139
    140    @SuppressWarnings("unchecked")
    141    private Map getMappedConfiguration(Type genericType)
    142    {
    143        Class keyType = findParameterizedTypeFromGenericType(genericType, 0);
    144        Class valueType = findParameterizedTypeFromGenericType(genericType, 1);
    145
    146        if (keyType == null || valueType == null)
    147            throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(genericType));
    148
    149        return resources.getMappedConfiguration(keyType, valueType);
    150    }
    151
    152    /**
    153     * Extracts from a generic type the underlying parameterized type. I.e., for List<Runnable>, will return Runnable.
    154     * This is limited to simple parameterized types, not the more complex cases involving wildcards and upper/lower
    155     * boundaries.
    156     *
    157     * @param type the genetic type of the parameter, i.e., List<Runnable>
    158     * @return the parameterize type (i.e. Runnable.class if type represents List<Runnable>).
    159     */
    160
    161    // package private for testing
    162    static Class findParameterizedTypeFromGenericType(Type type)
    163    {
    164        Class result = findParameterizedTypeFromGenericType(type, 0);
    165
    166        if (result == null) throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(type));
    167
    168        return result;
    169    }
    170
    171    /**
    172     * "Sniffs" a generic type to find the underlying parameterized type. If the Type is a class, then Object.class is
    173     * returned. Otherwise, the type must be a ParameterizedType. We check to make sure it has the correct number of a
    174     * actual types (1 for a Collection or List, 2 for a Map). The actual types must be classes (wildcards just aren't
    175     * supported)
    176     *
    177     * @param type      a Class or ParameterizedType to inspect
    178     * @param typeIndex the index within the ParameterizedType to extract
    179     * @return the actual type, or Object.class if the input type is not generic, or null if any other pre-condition is
    180     *         not met
    181     */
    182    private static Class findParameterizedTypeFromGenericType(Type type, int typeIndex)
    183    {
    184        // For a raw Class type, it means the parameter is not parameterized (i.e. Collection, not
    185        // Collection<Foo>), so we can return Object.class to allow no restriction.
    186
    187        if (type instanceof Class) return Object.class;
    188
    189        if (!(type instanceof ParameterizedType)) return null;
    190
    191        ParameterizedType pt = (ParameterizedType) type;
    192
    193        Type[] types = pt.getActualTypeArguments();
    194
    195        Type actualType = types[typeIndex];
    196
    197        return actualType instanceof Class ? (Class) actualType : null;
    198    }
    199}