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}