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