    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}