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 }