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