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.util.Arrays;
032import java.util.Set;
033
034@SuppressWarnings("all")
035public 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}