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