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}