001: // Copyright 2006, 2007 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.tapestry.internal.services;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
019:
020: import java.net.URL;
021: import java.util.Map;
022: import java.util.Set;
023:
024: import javassist.CannotCompileException;
025: import javassist.ClassPath;
026: import javassist.ClassPool;
027: import javassist.CtClass;
028: import javassist.Loader;
029: import javassist.LoaderClassPath;
030: import javassist.NotFoundException;
031: import javassist.Translator;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
035: import org.apache.tapestry.internal.events.UpdateListener;
036: import org.apache.tapestry.internal.util.URLChangeTracker;
037: import org.apache.tapestry.ioc.internal.services.ClassFactoryClassPool;
038: import org.apache.tapestry.ioc.internal.services.ClassFactoryImpl;
039: import org.apache.tapestry.ioc.internal.util.Defense;
040: import org.apache.tapestry.ioc.services.ClassFactory;
041:
042: /**
043: * A wrapper around a Javassist class loader that allows certain classes to be modified as they are
044: * loaded.
045: */
046: public final class ComponentInstantiatorSourceImpl extends
047: InvalidationEventHubImpl implements Translator,
048: ComponentInstantiatorSource, UpdateListener {
049: /**
050: * Add -Djavassist-write-dir=target/transformed-classes to the command line to force output of
051: * transformed classes to disk (for hardcore debugging).
052: */
053: private static final String JAVASSIST_WRITE_DIR = System
054: .getProperty("javassist-write-dir");
055:
056: private final Set<String> _controlledPackageNames = newSet();
057:
058: private final URLChangeTracker _changeTracker = new URLChangeTracker();
059:
060: private final ClassLoader _parent;
061:
062: private ClassFactoryClassPool _classPool;
063:
064: private Loader _loader;
065:
066: private final ComponentClassTransformer _transformer;
067:
068: private final Log _log;
069:
070: private ClassFactory _classFactory;
071:
072: /** Map from class name to Instantiator. */
073: private final Map<String, Instantiator> _instantiatorMap = newMap();
074:
075: private class PackageAwareLoader extends Loader {
076: public PackageAwareLoader(ClassLoader parent,
077: ClassPool classPool) {
078: super (parent, classPool);
079: }
080:
081: @Override
082: protected Class findClass(String className)
083: throws ClassNotFoundException {
084: if (inControlledPackage(className))
085: return super .findClass(className);
086:
087: // Returning null forces delegation to the parent class loader.
088:
089: return null;
090: }
091:
092: }
093:
094: public ComponentInstantiatorSourceImpl(ClassLoader parent,
095: ComponentClassTransformer transformer, Log log) {
096: _parent = parent;
097: _transformer = transformer;
098: _log = log;
099:
100: initializeService();
101: }
102:
103: public synchronized void checkForUpdates() {
104: if (!_changeTracker.containsChanges())
105: return;
106:
107: _changeTracker.clear();
108: _instantiatorMap.clear();
109:
110: // Release the existing class pool, loader and so forth.
111: // Create a new one.
112:
113: initializeService();
114:
115: // Tell everyone that the world has changed and they should discard
116: // their cache.
117:
118: fireInvalidationEvent();
119: }
120:
121: /**
122: * Invoked at object creation, or when there are updates to class files (i.e., invalidation), to
123: * create a new set of Javassist class pools and loaders.
124: */
125: private void initializeService() {
126: _classPool = new ClassFactoryClassPool(_parent);
127:
128: _loader = new PackageAwareLoader(_parent, _classPool);
129:
130: ClassPath path = new LoaderClassPath(_loader);
131:
132: _classPool.appendClassPath(path);
133:
134: try {
135: _loader.addTranslator(_classPool, this );
136: } catch (Exception ex) {
137: throw new RuntimeException(ex);
138: }
139:
140: _classFactory = new ClassFactoryImpl(_loader, _classPool, _log);
141: }
142:
143: // This is called from well within a synchronized block.
144: public void onLoad(ClassPool pool, String classname)
145: throws NotFoundException, CannotCompileException {
146: _log.debug("BEGIN onLoad " + classname);
147:
148: // This is our chance to make changes to the CtClass before it is loaded into memory.
149:
150: String diag = "FAIL";
151:
152: // If we are loading a class, it is because it is in a controlled package. There may be
153: // errors in the class that keep it from loading. By adding it to the change tracker
154: // early, we ensure that when the class is fixed, the change is picked up. Originally,
155: // this code was at the end of the method, and classes that contained errors would not be
156: // reloaded even after the code was fixed.
157:
158: addClassFileToChangeTracker(classname);
159:
160: try {
161: CtClass ctClass = pool.get(classname);
162:
163: // Force the creation of the super-class before the target class.
164:
165: forceSuperclassTransform(ctClass);
166:
167: // Do the transformations here
168:
169: _transformer.transformComponentClass(ctClass, _loader);
170:
171: writeClassToFileSystemForHardCoreDebuggingPurposesOnly(ctClass);
172:
173: diag = "END";
174: } catch (ClassNotFoundException ex) {
175: throw new CannotCompileException(ex);
176: } finally {
177: _log.debug(String.format("%5s onLoad %s", diag, classname));
178: }
179: }
180:
181: private void writeClassToFileSystemForHardCoreDebuggingPurposesOnly(
182: CtClass ctClass) {
183: if (JAVASSIST_WRITE_DIR == null)
184: return;
185:
186: try {
187: boolean p = ctClass.stopPruning(true);
188: ctClass.writeFile(JAVASSIST_WRITE_DIR);
189: ctClass.defrost();
190: ctClass.stopPruning(p);
191:
192: } catch (Exception ex) {
193: throw new RuntimeException(ex);
194: }
195: }
196:
197: private void addClassFileToChangeTracker(String classname) {
198: String path = classname.replace('.', '/') + ".class";
199:
200: URL url = _loader.getResource(path);
201:
202: _changeTracker.add(url);
203: }
204:
205: private void forceSuperclassTransform(CtClass ctClass)
206: throws NotFoundException, ClassNotFoundException {
207: CtClass super Class = ctClass.getSuperclass();
208:
209: findClass(super Class.getName());
210: }
211:
212: /** Does nothing. */
213: public void start(ClassPool pool) throws NotFoundException,
214: CannotCompileException {
215: }
216:
217: public synchronized Instantiator findInstantiator(String classname) {
218: Instantiator result = _instantiatorMap.get(classname);
219:
220: if (result == null) {
221: Class instanceClass = findClass(classname);
222:
223: result = _transformer.createInstantiator(instanceClass);
224:
225: _instantiatorMap.put(classname, result);
226: }
227:
228: return result;
229: }
230:
231: private Class findClass(String classname) {
232: try {
233: return _loader.loadClass(classname);
234: } catch (ClassNotFoundException ex) {
235: throw new RuntimeException(ex);
236: }
237: }
238:
239: /**
240: * Returns true if the package for the class name is in a package that is controlled by the
241: * enhancer. Controlled packages are identified by {@link #addPackage(String)}.
242: */
243:
244: boolean inControlledPackage(String classname) {
245: String packageName = stripTail(classname);
246:
247: while (packageName != null) {
248: if (_controlledPackageNames.contains(packageName))
249: return true;
250:
251: packageName = stripTail(packageName);
252: }
253:
254: return false;
255: }
256:
257: private String stripTail(String input) {
258: int lastdot = input.lastIndexOf('.');
259:
260: if (lastdot < 0)
261: return null;
262:
263: return input.substring(0, lastdot);
264: }
265:
266: // synchronized may be overkill, but that's ok.
267: public synchronized void addPackage(String packageName) {
268: Defense.notBlank(packageName, "packageName");
269:
270: // TODO: Should we check that packages are not nested?
271:
272: _controlledPackageNames.add(packageName);
273: }
274:
275: public boolean exists(String className) {
276: String path = className.replace(".", "/") + ".class";
277:
278: return _parent.getResource(path) != null;
279: }
280:
281: public ClassFactory getClassFactory() {
282: return _classFactory;
283: }
284: }
|