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            @Override
151            public ObjectCreator constructCreator(ServiceBuilderResources resources)
152            {
153                return new ConstructorServiceCreator(resources, getDescription(), constructor);
154            }
155
156            @Override
157            public String getDescription()
158            {
159                return String.format("%s via %s", proxyFactory.getConstructorLocation(constructor),
160                        proxyFactory.getMethodLocation(bindMethod));
161            }
162        };
163    }
164
165    private ObjectCreatorSource createReloadableConstructorBasedObjectCreatorSource()
166    {
167        return new ReloadableObjectCreatorSource(proxyFactory, bindMethod, serviceInterface, serviceImplementation,
168                eagerLoad);
169    }
170
171    @Override
172    @SuppressWarnings("unchecked")
173    public <T> ServiceBindingOptions bind(Class<T> serviceClass)
174    {
175        if (serviceClass.isInterface())
176        {
177            try
178            {
179                String expectedImplName = serviceClass.getName() + "Impl";
180
181                ClassLoader classLoader = proxyFactory.getClassLoader();
182
183                Class<T> implementationClass = (Class<T>) classLoader.loadClass(expectedImplName);
184
185                if (!implementationClass.isInterface() && serviceClass.isAssignableFrom(implementationClass))
186                {
187                    return bind(
188                            serviceClass, implementationClass);
189                }
190                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceClass));
191            } catch (ClassNotFoundException ex)
192            {
193                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.",
194                        serviceClass.getName()));
195            }
196        }
197
198        return bind(serviceClass, serviceClass);
199    }
200
201    @Override
202    public <T> ServiceBindingOptions bind(Class<T> serviceInterface, final ServiceBuilder<T> builder)
203    {
204        assert serviceInterface != null;
205        assert builder != null;
206        lock.check();
207
208        flush();
209
210        this.serviceInterface = serviceInterface;
211        this.scope = ScopeConstants.DEFAULT;
212
213        serviceId = serviceInterface.getSimpleName();
214
215        this.source = new ObjectCreatorSource()
216        {
217            @Override
218            public ObjectCreator constructCreator(final ServiceBuilderResources resources)
219            {
220                return new ObjectCreator()
221                {
222                    @Override
223                    public Object createObject()
224                    {
225                        return builder.buildService(resources);
226                    }
227                };
228            }
229
230            @Override
231            public String getDescription()
232            {
233                return proxyFactory.getMethodLocation(bindMethod).toString();
234            }
235        };
236
237        return this;
238    }
239
240    @Override
241    public <T> ServiceBindingOptions bind(Class<T> serviceInterface, Class<? extends T> serviceImplementation)
242    {
243        assert serviceInterface != null;
244        assert serviceImplementation != null;
245        lock.check();
246
247        flush();
248
249        this.serviceInterface = serviceInterface;
250
251        this.serviceImplementation = serviceImplementation;
252
253        // Set defaults for the other properties.
254
255        eagerLoad = serviceImplementation.getAnnotation(EagerLoad.class) != null;
256
257        serviceId = InternalUtils.getServiceId(serviceImplementation);
258
259        if (serviceId == null)
260        {
261            serviceId = serviceInterface.getSimpleName();
262        }
263
264        Scope scope = serviceImplementation.getAnnotation(Scope.class);
265
266        this.scope = scope != null ? scope.value() : ScopeConstants.DEFAULT;
267
268        Marker marker = serviceImplementation.getAnnotation(Marker.class);
269
270        if (marker != null)
271        {
272            InternalUtils.validateMarkerAnnotations(marker.value());
273            markers.addAll(Arrays.asList(marker.value()));
274        }
275
276        preventDecoration |= serviceImplementation.getAnnotation(PreventServiceDecoration.class) != null;
277
278        return this;
279    }
280
281    @Override
282    public ServiceBindingOptions eagerLoad()
283    {
284        lock.check();
285
286        eagerLoad = true;
287
288        return this;
289    }
290
291    @Override
292    public ServiceBindingOptions preventDecoration()
293    {
294        lock.check();
295
296        preventDecoration = true;
297
298        return this;
299    }
300
301    @Override
302    public ServiceBindingOptions preventReloading()
303    {
304        lock.check();
305
306        preventReloading = true;
307
308        return this;
309    }
310
311    @Override
312    public ServiceBindingOptions withId(String id)
313    {
314        assert InternalUtils.isNonBlank(id);
315        lock.check();
316
317        serviceId = id;
318
319        return this;
320    }
321
322    @Override
323    public ServiceBindingOptions withSimpleId()
324    {
325        if (serviceImplementation == null)
326        {
327            throw new IllegalArgumentException("No defined implementation class to generate simple id from.");
328        }
329
330        return withId(serviceImplementation.getSimpleName());
331    }
332
333    @Override
334    public ServiceBindingOptions scope(String scope)
335    {
336        assert InternalUtils.isNonBlank(scope);
337        lock.check();
338
339        this.scope = scope;
340
341        return this;
342    }
343
344    @Override
345    public ServiceBindingOptions withMarker(Class<? extends Annotation>... marker)
346    {
347        lock.check();
348
349        InternalUtils.validateMarkerAnnotations(marker);
350
351        markers.addAll(Arrays.asList(marker));
352
353        return this;
354    }
355}