    001// Copyright 2007, 2008, 2009, 2010, 2011 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.commons.*;
    018import org.apache.tapestry5.commons.services.PlasticProxyFactory;
    019import org.apache.tapestry5.commons.util.CollectionFactory;
    020import org.apache.tapestry5.ioc.ScopeConstants;
    021import org.apache.tapestry5.ioc.ServiceBinder;
    022import org.apache.tapestry5.ioc.ServiceBindingOptions;
    023import org.apache.tapestry5.ioc.ServiceBuilder;
    024import org.apache.tapestry5.ioc.ServiceBuilderResources;
    025import org.apache.tapestry5.ioc.annotations.EagerLoad;
    026import org.apache.tapestry5.ioc.annotations.Marker;
    027import org.apache.tapestry5.ioc.annotations.PreventServiceDecoration;
    028import org.apache.tapestry5.ioc.annotations.Scope;
    029import org.apache.tapestry5.ioc.def.ServiceDef;
    030import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    031import org.apache.tapestry5.ioc.internal.util.OneShotLock;
    032
    033import java.lang.annotation.Annotation;
    034import java.lang.reflect.Constructor;
    035import java.lang.reflect.Method;
    036import java.lang.reflect.Modifier;
    037import java.util.Arrays;
    038import java.util.Set;
    039
    040@SuppressWarnings("all")
    041public class ServiceBinderImpl implements ServiceBinder, ServiceBindingOptions
    042{
    043    private final OneShotLock lock = new OneShotLock();
    044
    045    private final Method bindMethod;
    046
    047    private final ServiceDefAccumulator accumulator;
    048
    049    private PlasticProxyFactory proxyFactory;
    050
    051    private final Set<Class> defaultMarkers;
    052
    053    private final boolean moduleDefaultPreventDecoration;
    054
    055    public ServiceBinderImpl(ServiceDefAccumulator accumulator, Method bindMethod, PlasticProxyFactory proxyFactory,
    056                             Set<Class> defaultMarkers, boolean moduleDefaultPreventDecoration)
    057    {
    058        this.accumulator = accumulator;
    059        this.bindMethod = bindMethod;
    060        this.proxyFactory = proxyFactory;
    061        this.defaultMarkers = defaultMarkers;
    062        this.moduleDefaultPreventDecoration = moduleDefaultPreventDecoration;
    063
    064        clear();
    065    }
    066
    067    private String serviceId;
    068
    069    private Class serviceInterface;
    070
    071    private Class serviceImplementation;
    072
    073    private final Set<Class> markers = CollectionFactory.newSet();
    074
    075    private ObjectCreatorSource source;
    076
    077    private boolean eagerLoad;
    078
    079    private String scope;
    080
    081    private boolean preventDecoration;
    082
    083    private boolean preventReloading;
    084
    085    public void finish()
    086    {
    087        lock.lock();
    088
    089        flush();
    090    }
    091
    092    protected void flush()
    093    {
    094        if (serviceInterface == null)
    095            return;
    096
    097        // source will be null when the implementation class is provided; non-null when using
    098        // a ServiceBuilder callback
    099
    100        if (source == null)
    101            source = createObjectCreatorSourceFromImplementationClass();
    102
    103        // Combine service-specific markers with those inherited form the module.
    104        Set<Class> markers = CollectionFactory.newSet(defaultMarkers);
    105        markers.addAll(this.markers);
    106
    107        ServiceDef serviceDef = new ServiceDefImpl(serviceInterface, serviceImplementation, serviceId, markers, scope,
    108                eagerLoad, preventDecoration, source);
    109
    110        accumulator.addServiceDef(serviceDef);
    111
    112        clear();
    113    }
    114
    115    private void clear()
    116    {
    117        serviceId = null;
    118        serviceInterface = null;
    119        serviceImplementation = null;
    120        source = null;
    121        this.markers.clear();
    122        eagerLoad = false;
    123        scope = null;
    124        preventDecoration = moduleDefaultPreventDecoration;
    125        preventReloading = false;
    126    }
    127
    128    private ObjectCreatorSource createObjectCreatorSourceFromImplementationClass()
    129    {
    130        if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && !preventReloading && isProxiable() && reloadableScope()
    131                && InternalUtils.isLocalFile(serviceImplementation))
    132            return createReloadableConstructorBasedObjectCreatorSource();
    133
    134        return createStandardConstructorBasedObjectCreatorSource();
    135    }
    136
    137    private boolean isProxiable()
    138    {
    139        return serviceInterface.isInterface();
    140    }
    141
    142    private boolean reloadableScope()
    143    {
    144        return scope.equalsIgnoreCase(ScopeConstants.DEFAULT);
    145    }
    146
    147    private ObjectCreatorSource createStandardConstructorBasedObjectCreatorSource()
    148    {
    149        if (Modifier.isAbstract(serviceImplementation.getModifiers()))
    150            throw new RuntimeException(IOCMessages.abstractServiceImplementation(serviceImplementation, serviceId));
    151        final Constructor constructor = InternalUtils.findAutobuildConstructor(serviceImplementation);
    152
    153        if (constructor == null)
    154            throw new RuntimeException(IOCMessages.noConstructor(serviceImplementation, serviceId));
    155
    156        return new ObjectCreatorSource()
    157        {
    158            @Override
    159            public ObjectCreator constructCreator(ServiceBuilderResources resources)
    160            {
    161                return new ConstructorServiceCreator(resources, getDescription(), constructor);
    162            }
    163
    164            @Override
    165            public String getDescription()
    166            {
    167                return String.format("%s via %s", proxyFactory.getConstructorLocation(constructor),
    168                        proxyFactory.getMethodLocation(bindMethod));
    169            }
    170        };
    171    }
    172
    173    private ObjectCreatorSource createReloadableConstructorBasedObjectCreatorSource()
    174    {
    175        return new ReloadableObjectCreatorSource(proxyFactory, bindMethod, serviceInterface, serviceImplementation,
    176                eagerLoad);
    177    }
    178
    179    @Override
    180    @SuppressWarnings("unchecked")
    181    public <T> ServiceBindingOptions bind(Class<T> serviceClass)
    182    {
    183        if (serviceClass.isInterface())
    184        {
    185            try
    186            {
    187                String expectedImplName = serviceClass.getName() + "Impl";
    188
    189                ClassLoader classLoader = proxyFactory.getClassLoader();
    190
    191                Class<T> implementationClass = (Class<T>) classLoader.loadClass(expectedImplName);
    192
    193                if (!implementationClass.isInterface() && serviceClass.isAssignableFrom(implementationClass))
    194                {
    195                    return bind(
    196                            serviceClass, implementationClass);
    197                }
    198                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceClass));
    199            } catch (ClassNotFoundException ex)
    200            {
    201                throw new RuntimeException(String.format("Could not find default implementation class %sImpl. Please provide this class, or bind the service interface to a specific implementation class.",
    202                        serviceClass.getName()));
    203            }
    204        }
    205
    206        return bind(serviceClass, serviceClass);
    207    }
    208
    209    @Override
    210    public <T> ServiceBindingOptions bind(Class<T> serviceInterface, final ServiceBuilder<T> builder)
    211    {
    212        assert serviceInterface != null;
    213        assert builder != null;
    214        lock.check();
    215
    216        flush();
    217
    218        this.serviceInterface = serviceInterface;
    219        this.scope = ScopeConstants.DEFAULT;
    220
    221        serviceId = serviceInterface.getSimpleName();
    222
    223        this.source = new ObjectCreatorSource()
    224        {
    225            @Override
    226            public ObjectCreator constructCreator(final ServiceBuilderResources resources)
    227            {
    228                return new ObjectCreator()
    229                {
    230                    @Override
    231                    public Object createObject()
    232                    {
    233                        return builder.buildService(resources);
    234                    }
    235                };
    236            }
    237
    238            @Override
    239            public String getDescription()
    240            {
    241                return proxyFactory.getMethodLocation(bindMethod).toString();
    242            }
    243        };
    244
    245        return this;
    246    }
    247
    248    @Override
    249    public <T> ServiceBindingOptions bind(Class<T> serviceInterface, Class<? extends T> serviceImplementation)
    250    {
    251        assert serviceInterface != null;
    252        assert serviceImplementation != null;
    253        lock.check();
    254
    255        flush();
    256
    257        this.serviceInterface = serviceInterface;
    258
    259        this.serviceImplementation = serviceImplementation;
    260
    261        // Set defaults for the other properties.
    262
    263        eagerLoad = serviceImplementation.getAnnotation(EagerLoad.class) != null;
    264
    265        serviceId = InternalUtils.getServiceId(serviceImplementation);
    266
    267        if (serviceId == null)
    268        {
    269            serviceId = serviceInterface.getSimpleName();
    270        }
    271
    272        Scope scope = serviceImplementation.getAnnotation(Scope.class);
    273
    274        this.scope = scope != null ? scope.value() : ScopeConstants.DEFAULT;
    275
    276        Marker marker = serviceImplementation.getAnnotation(Marker.class);
    277
    278        if (marker != null)
    279        {
    280            InternalUtils.validateMarkerAnnotations(marker.value());
    281            markers.addAll(Arrays.asList(marker.value()));
    282        }
    283
    284        preventDecoration |= serviceImplementation.getAnnotation(PreventServiceDecoration.class) != null;
    285
    286        return this;
    287    }
    288
    289    @Override
    290    public ServiceBindingOptions eagerLoad()
    291    {
    292        lock.check();
    293
    294        eagerLoad = true;
    295
    296        return this;
    297    }
    298
    299    @Override
    300    public ServiceBindingOptions preventDecoration()
    301    {
    302        lock.check();
    303
    304        preventDecoration = true;
    305
    306        return this;
    307    }
    308
    309    @Override
    310    public ServiceBindingOptions preventReloading()
    311    {
    312        lock.check();
    313
    314        preventReloading = true;
    315
    316        return this;
    317    }
    318
    319    @Override
    320    public ServiceBindingOptions withId(String id)
    321    {
    322        assert InternalUtils.isNonBlank(id);
    323        lock.check();
    324
    325        serviceId = id;
    326
    327        return this;
    328    }
    329
    330    @Override
    331    public ServiceBindingOptions withSimpleId()
    332    {
    333        if (serviceImplementation == null)
    334        {
    335            throw new IllegalArgumentException("No defined implementation class to generate simple id from.");
    336        }
    337
    338        return withId(serviceImplementation.getSimpleName());
    339    }
    340
    341    @Override
    342    public ServiceBindingOptions scope(String scope)
    343    {
    344        assert InternalUtils.isNonBlank(scope);
    345        lock.check();
    346
    347        this.scope = scope;
    348
    349        return this;
    350    }
    351
    352    @Override
    353    public ServiceBindingOptions withMarker(Class<? extends Annotation>... marker)
    354    {
    355        lock.check();
    356
    357        InternalUtils.validateMarkerAnnotations(marker);
    358
    359        markers.addAll(Arrays.asList(marker));
    360
    361        return this;
    362    }
    363}