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 }