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