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    }