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 }