001 // Copyright 2006, 2007, 2008, 2010, 2011, 2012 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.internal.services; 016 017 import org.apache.tapestry5.ComponentResources; 018 import org.apache.tapestry5.SymbolConstants; 019 import org.apache.tapestry5.internal.InternalComponentResources; 020 import org.apache.tapestry5.internal.InternalConstants; 021 import org.apache.tapestry5.internal.model.MutableComponentModelImpl; 022 import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 023 import org.apache.tapestry5.ioc.Invokable; 024 import org.apache.tapestry5.ioc.LoggerSource; 025 import org.apache.tapestry5.ioc.OperationTracker; 026 import org.apache.tapestry5.ioc.Resource; 027 import org.apache.tapestry5.ioc.annotations.PostInjection; 028 import org.apache.tapestry5.ioc.annotations.Primary; 029 import org.apache.tapestry5.ioc.annotations.Symbol; 030 import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl; 031 import org.apache.tapestry5.ioc.internal.services.PlasticProxyFactoryImpl; 032 import org.apache.tapestry5.ioc.internal.util.ClasspathResource; 033 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 034 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 035 import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; 036 import org.apache.tapestry5.ioc.services.Builtin; 037 import org.apache.tapestry5.ioc.services.ClassFactory; 038 import org.apache.tapestry5.ioc.services.ClasspathURLConverter; 039 import org.apache.tapestry5.ioc.services.PlasticProxyFactory; 040 import org.apache.tapestry5.model.ComponentModel; 041 import org.apache.tapestry5.model.MutableComponentModel; 042 import org.apache.tapestry5.plastic.*; 043 import org.apache.tapestry5.plastic.PlasticManager.PlasticManagerBuilder; 044 import org.apache.tapestry5.runtime.Component; 045 import org.apache.tapestry5.runtime.ComponentEvent; 046 import org.apache.tapestry5.runtime.ComponentResourcesAware; 047 import org.apache.tapestry5.runtime.PageLifecycleListener; 048 import org.apache.tapestry5.services.*; 049 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 050 import org.apache.tapestry5.services.transform.ControlledPackageType; 051 import org.apache.tapestry5.services.transform.TransformationSupport; 052 import org.slf4j.Logger; 053 054 import java.util.Map; 055 import java.util.Set; 056 057 /** 058 * A wrapper around a {@link PlasticManager} that allows certain classes to be modified as they are loaded. 059 */ 060 public final class ComponentInstantiatorSourceImpl implements ComponentInstantiatorSource, UpdateListener, 061 InvalidationListener, PlasticManagerDelegate, PlasticClassListener 062 { 063 private final Set<String> controlledPackageNames = CollectionFactory.newSet(); 064 065 private final URLChangeTracker changeTracker; 066 067 private final ClassLoader parent; 068 069 private final ComponentClassTransformWorker2 transformerChain; 070 071 private final LoggerSource loggerSource; 072 073 private final Logger logger; 074 075 private final OperationTracker tracker; 076 077 private final InternalComponentInvalidationEventHub invalidationHub; 078 079 private final boolean productionMode; 080 081 private final ComponentClassResolver resolver; 082 083 // These change whenever the invalidation event hub sends an invalidation notification 084 085 private volatile ClassFactory classFactory; 086 087 private volatile PlasticProxyFactory proxyFactory; 088 089 private volatile PlasticManager manager; 090 091 /** 092 * Map from class name to Instantiator. 093 */ 094 private final Map<String, Instantiator> classToInstantiator = CollectionFactory.newMap(); 095 096 private final Map<String, ComponentModel> classToModel = CollectionFactory.newMap(); 097 098 private final MethodDescription GET_COMPONENT_RESOURCES = PlasticUtils.getMethodDescription( 099 ComponentResourcesAware.class, "getComponentResources"); 100 101 private final ConstructorCallback REGISTER_AS_PAGE_LIFECYCLE_LISTENER = new ConstructorCallback() 102 { 103 public void onConstruct(Object instance, InstanceContext context) 104 { 105 InternalComponentResources resources = context.get(InternalComponentResources.class); 106 107 resources.addPageLifecycleListener((PageLifecycleListener) instance); 108 } 109 }; 110 111 public ComponentInstantiatorSourceImpl(Logger logger, 112 113 LoggerSource loggerSource, 114 115 @Builtin 116 PlasticProxyFactory proxyFactory, 117 118 @Primary 119 ComponentClassTransformWorker2 transformerChain, 120 121 ClasspathURLConverter classpathURLConverter, 122 123 OperationTracker tracker, 124 125 Map<String, ControlledPackageType> configuration, 126 127 @Symbol(SymbolConstants.PRODUCTION_MODE) 128 boolean productionMode, 129 130 ComponentClassResolver resolver, 131 132 InternalComponentInvalidationEventHub invalidationHub) 133 { 134 this.parent = proxyFactory.getClassLoader(); 135 this.transformerChain = transformerChain; 136 this.logger = logger; 137 this.loggerSource = loggerSource; 138 this.changeTracker = new URLChangeTracker(classpathURLConverter); 139 this.tracker = tracker; 140 this.invalidationHub = invalidationHub; 141 this.productionMode = productionMode; 142 this.resolver = resolver; 143 144 // For now, we just need the keys of the configuration. When there are more types of controlled 145 // packages, we'll need to do more. 146 147 controlledPackageNames.addAll(configuration.keySet()); 148 149 initializeService(); 150 } 151 152 @PostInjection 153 public void listenForUpdates(UpdateListenerHub hub) 154 { 155 invalidationHub.addInvalidationListener(this); 156 hub.addUpdateListener(this); 157 } 158 159 public synchronized void checkForUpdates() 160 { 161 if (changeTracker.containsChanges()) 162 { 163 invalidationHub.classInControlledPackageHasChanged(); 164 } 165 } 166 167 public void forceComponentInvalidation() 168 { 169 changeTracker.clear(); 170 invalidationHub.classInControlledPackageHasChanged(); 171 } 172 173 public void objectWasInvalidated() 174 { 175 changeTracker.clear(); 176 classToInstantiator.clear(); 177 proxyFactory.clearCache(); 178 179 // Release the existing class pool, loader and so forth. 180 // Create a new one. 181 182 initializeService(); 183 } 184 185 /** 186 * Invoked at object creation, or when there are updates to class files (i.e., invalidation), to create a new set of 187 * Javassist class pools and loaders. 188 */ 189 private void initializeService() 190 { 191 PlasticManagerBuilder builder = PlasticManager.withClassLoader(parent).delegate(this) 192 .packages(controlledPackageNames); 193 194 if (!productionMode) 195 { 196 builder.enable(TransformationOption.FIELD_WRITEBEHIND); 197 } 198 199 manager = builder.create(); 200 201 manager.addPlasticClassListener(this); 202 203 classFactory = new ClassFactoryImpl(manager.getClassLoader(), logger); 204 205 proxyFactory = new PlasticProxyFactoryImpl(manager, logger); 206 207 classToInstantiator.clear(); 208 classToModel.clear(); 209 } 210 211 public synchronized Instantiator getInstantiator(final String className) 212 { 213 Instantiator result = classToInstantiator.get(className); 214 215 if (result == null) 216 { 217 result = createInstantiatorForClass(className); 218 219 classToInstantiator.put(className, result); 220 } 221 222 return result; 223 } 224 225 private Instantiator createInstantiatorForClass(final String className) 226 { 227 return tracker.invoke(String.format("Creating instantiator for component class %s", className), 228 new Invokable<Instantiator>() 229 { 230 public Instantiator invoke() 231 { 232 // Force the creation of the class (and the transformation of the class). This will first 233 // trigger transformations of any base classes. 234 235 final ClassInstantiator<Component> plasticInstantiator = manager.getClassInstantiator(className); 236 237 final ComponentModel model = classToModel.get(className); 238 239 return new Instantiator() 240 { 241 public Component newInstance(InternalComponentResources resources) 242 { 243 return plasticInstantiator.with(ComponentResources.class, resources) 244 .with(InternalComponentResources.class, resources).newInstance(); 245 } 246 247 public ComponentModel getModel() 248 { 249 return model; 250 } 251 252 @Override 253 public String toString() 254 { 255 return String.format("[Instantiator[%s]", className); 256 } 257 }; 258 } 259 }); 260 } 261 262 public boolean exists(String className) 263 { 264 return parent.getResource(PlasticInternalUtils.toClassPath(className)) != null; 265 } 266 267 public ClassFactory getClassFactory() 268 { 269 return classFactory; 270 } 271 272 public PlasticProxyFactory getProxyFactory() 273 { 274 return proxyFactory; 275 } 276 277 public void transform(final PlasticClass plasticClass) 278 { 279 tracker.run(String.format("Running component class transformations on %s", plasticClass.getClassName()), 280 new Runnable() 281 { 282 public void run() 283 { 284 String className = plasticClass.getClassName(); 285 String parentClassName = plasticClass.getSuperClassName(); 286 287 // The parent model may not exist, if the super class is not in a controlled package. 288 289 ComponentModel parentModel = classToModel.get(parentClassName); 290 291 final boolean isRoot = parentModel == null; 292 293 if (isRoot 294 && !(parentClassName.equals("java.lang.Object") || parentClassName 295 .equals("groovy.lang.GroovyObjectSupport"))) 296 { 297 String suggestedPackageName = buildSuggestedPackageName(className); 298 299 throw new RuntimeException(ServicesMessages.baseClassInWrongPackage(parentClassName, 300 className, suggestedPackageName)); 301 } 302 303 // Tapestry 5.2 was more sensitive that the parent class have a public no-args constructor. 304 // Plastic 305 // doesn't care, and we don't have the tools to dig that information out. 306 307 Logger logger = loggerSource.getLogger(className); 308 309 Resource baseResource = new ClasspathResource(parent, PlasticInternalUtils 310 .toClassPath(className)); 311 312 changeTracker.add(baseResource.toURL()); 313 314 if (isRoot) 315 { 316 implementComponentInterface(plasticClass); 317 } 318 319 boolean isPage = resolver.isPage(className); 320 321 boolean superClassImplementsPageLifecycle = plasticClass.isInterfaceImplemented(PageLifecycleListener.class); 322 323 final MutableComponentModel model = new MutableComponentModelImpl(className, logger, baseResource, 324 parentModel, isPage); 325 326 transformerChain.transform(plasticClass, new TransformationSupportImpl(plasticClass, isRoot, model), model); 327 328 if (!superClassImplementsPageLifecycle && plasticClass.isInterfaceImplemented(PageLifecycleListener.class)) 329 { 330 plasticClass.onConstruct(REGISTER_AS_PAGE_LIFECYCLE_LISTENER); 331 } 332 333 classToModel.put(className, model); 334 } 335 }); 336 } 337 338 private void implementComponentInterface(PlasticClass plasticClass) 339 { 340 plasticClass.introduceInterface(Component.class); 341 342 final PlasticField resourcesField = plasticClass.introduceField(InternalComponentResources.class, 343 "internalComponentResources").injectFromInstanceContext(); 344 345 plasticClass.introduceMethod(GET_COMPONENT_RESOURCES, new InstructionBuilderCallback() 346 { 347 public void doBuild(InstructionBuilder builder) 348 { 349 builder.loadThis().getField(resourcesField).returnResult(); 350 } 351 }); 352 } 353 354 public <T> ClassInstantiator<T> configureInstantiator(String className, ClassInstantiator<T> instantiator) 355 { 356 return instantiator; 357 } 358 359 private String buildSuggestedPackageName(String className) 360 { 361 for (String subpackage : InternalConstants.SUBPACKAGES) 362 { 363 String term = "." + subpackage + "."; 364 365 int pos = className.indexOf(term); 366 367 // Keep the leading '.' in the subpackage name and tack on "base". 368 369 if (pos > 0) 370 return className.substring(0, pos + 1) + InternalConstants.BASE_SUBPACKAGE; 371 } 372 373 // Is this even reachable? className should always be in a controlled package and so 374 // some subpackage above should have matched. 375 376 return null; 377 } 378 379 public void classWillLoad(PlasticClassEvent event) 380 { 381 Logger logger = loggerSource.getLogger("tapestry.transformer." + event.getPrimaryClassName()); 382 383 if (logger.isDebugEnabled()) 384 logger.debug(event.getDissasembledBytecode()); 385 } 386 387 private class TransformationSupportImpl implements TransformationSupport 388 { 389 private final PlasticClass plasticClass; 390 391 private final boolean root; 392 393 private final MutableComponentModel model; 394 395 public TransformationSupportImpl(PlasticClass plasticClass, boolean root, MutableComponentModel model) 396 { 397 this.plasticClass = plasticClass; 398 this.root = root; 399 this.model = model; 400 } 401 402 public Class toClass(String typeName) 403 { 404 try 405 { 406 return PlasticInternalUtils.toClass(manager.getClassLoader(), typeName); 407 } catch (ClassNotFoundException ex) 408 { 409 throw new RuntimeException(String.format( 410 "Unable to convert type '%s' to a Class: %s", typeName, 411 InternalUtils.toMessage(ex)), ex); 412 } 413 } 414 415 public boolean isRootTransformation() 416 { 417 return root; 418 } 419 420 public void addEventHandler(final String eventType, final int minContextValues, final String operationDescription, final ComponentEventHandler handler) 421 { 422 assert InternalUtils.isNonBlank(eventType); 423 assert minContextValues >= 0; 424 assert handler != null; 425 426 model.addEventHandler(eventType); 427 428 MethodAdvice advice = new EventMethodAdvice(tracker, eventType, minContextValues, operationDescription, handler); 429 430 plasticClass.introduceMethod(TransformConstants.DISPATCH_COMPONENT_EVENT_DESCRIPTION).addAdvice(advice); 431 } 432 } 433 434 private static class EventMethodAdvice implements MethodAdvice 435 { 436 final OperationTracker tracker; 437 final String eventType; 438 final int minContextValues; 439 final String operationDescription; 440 final ComponentEventHandler handler; 441 442 public EventMethodAdvice(OperationTracker tracker, String eventType, int minContextValues, String operationDescription, ComponentEventHandler handler) 443 { 444 this.tracker = tracker; 445 this.eventType = eventType; 446 this.minContextValues = minContextValues; 447 this.operationDescription = operationDescription; 448 this.handler = handler; 449 } 450 451 public void advise(MethodInvocation invocation) 452 { 453 final ComponentEvent event = (ComponentEvent) invocation.getParameter(0); 454 455 boolean matches = !event.isAborted() && event.matches(eventType, "", minContextValues); 456 457 if (matches) 458 { 459 final Component instance = (Component) invocation.getInstance(); 460 461 tracker.invoke(operationDescription, new Invokable<Object>() 462 { 463 public Object invoke() 464 { 465 handler.handleEvent(instance, event); 466 467 return null; 468 } 469 }); 470 } 471 472 // Order of operations is key here. This logic takes precedence; base class event dispatch and event handler methods 473 // in the class come AFTER. 474 475 invocation.proceed(); 476 477 if (matches) 478 { 479 invocation.setReturnValue(true); 480 } 481 } 482 }; 483 484 }