001 // Copyright 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.ioc.internal;
016
017 import org.apache.tapestry5.internal.plastic.ClassLoaderDelegate;
018 import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
019 import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
020 import org.apache.tapestry5.internal.plastic.asm.ClassReader;
021 import org.apache.tapestry5.internal.plastic.asm.ClassVisitor;
022 import org.apache.tapestry5.internal.plastic.asm.Opcodes;
023 import org.apache.tapestry5.ioc.Invokable;
024 import org.apache.tapestry5.ioc.ObjectCreator;
025 import org.apache.tapestry5.ioc.OperationTracker;
026 import org.apache.tapestry5.ioc.ReloadAware;
027 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
028 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
029 import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
030 import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
031 import org.apache.tapestry5.services.UpdateListener;
032 import org.slf4j.Logger;
033
034 import java.io.ByteArrayInputStream;
035 import java.io.ByteArrayOutputStream;
036 import java.io.IOException;
037 import java.io.InputStream;
038 import java.net.URL;
039 import java.util.Set;
040
041 @SuppressWarnings("all")
042 public abstract class AbstractReloadableObjectCreator implements ObjectCreator, UpdateListener, ClassLoaderDelegate
043 {
044 private final ClassLoader baseClassLoader;
045
046 private final String implementationClassName;
047
048 private final Logger logger;
049
050 private final OperationTracker tracker;
051
052 private final URLChangeTracker changeTracker = new URLChangeTracker();
053
054 private final PlasticProxyFactory proxyFactory;
055
056 /**
057 * The set of class names that should be loaded by the class loader. This is necessary to support
058 * reloading the class when a base class changes, and to properly support access to protected methods.
059 */
060 private final Set<String> classesToLoad = CollectionFactory.newSet();
061
062 private Object instance;
063
064 private boolean firstTime = true;
065
066 private PlasticClassLoader loader;
067
068 protected AbstractReloadableObjectCreator(PlasticProxyFactory proxyFactory, ClassLoader baseClassLoader, String implementationClassName,
069 Logger logger, OperationTracker tracker)
070 {
071 this.proxyFactory = proxyFactory;
072 this.baseClassLoader = baseClassLoader;
073 this.implementationClassName = implementationClassName;
074 this.logger = logger;
075 this.tracker = tracker;
076 }
077
078 public synchronized void checkForUpdates()
079 {
080 if (instance == null || !changeTracker.containsChanges())
081 {
082 return;
083 }
084
085 if (logger.isDebugEnabled())
086 {
087 logger.debug(String.format("Implementation class %s has changed and will be reloaded on next use.",
088 implementationClassName));
089 }
090
091 changeTracker.clear();
092
093 loader = null;
094
095 proxyFactory.clearCache();
096
097 boolean reloadNow = informInstanceOfReload();
098
099 instance = reloadNow ? createInstance() : null;
100 }
101
102 private boolean informInstanceOfReload()
103 {
104 if (instance instanceof ReloadAware)
105 {
106 ReloadAware ra = (ReloadAware) instance;
107
108 return ra.shutdownImplementationForReload();
109 }
110
111 return false;
112 }
113
114 public synchronized Object createObject()
115 {
116 if (instance == null)
117 {
118 instance = createInstance();
119 }
120
121 return instance;
122 }
123
124 private Object createInstance()
125 {
126 return tracker.invoke(String.format("Reloading class %s.", implementationClassName), new Invokable<Object>()
127 {
128 public Object invoke()
129 {
130 Class reloadedClass = reloadImplementationClass();
131
132 return createInstance(reloadedClass);
133 }
134 });
135 }
136
137 /**
138 * Invoked when an instance of the class is needed. It is the responsibility of this method (as implemented in a
139 * subclass) to instantiate the class and inject dependencies into the class.
140 *
141 * @see InternalUtils#findAutobuildConstructor(Class)
142 */
143 abstract protected Object createInstance(Class clazz);
144
145 private Class reloadImplementationClass()
146 {
147 if (logger.isDebugEnabled())
148 {
149 logger.debug(String.format("%s class %s.", firstTime ? "Loading" : "Reloading", implementationClassName));
150 }
151
152 loader = new PlasticClassLoader(baseClassLoader, this);
153
154 classesToLoad.clear();
155
156 add(implementationClassName);
157
158 try
159 {
160 Class result = loader.loadClass(implementationClassName);
161
162 firstTime = false;
163
164 return result;
165 } catch (Throwable ex)
166 {
167 throw new RuntimeException(String.format("Unable to %s class %s: %s", firstTime ? "load" : "reload",
168 implementationClassName, InternalUtils.toMessage(ex)), ex);
169 }
170 }
171
172 private void add(String className)
173 {
174 if (!classesToLoad.contains(className))
175 {
176 logger.debug(String.format("Marking class %s to be (re-)loaded", className));
177
178 classesToLoad.add(className);
179 }
180 }
181
182 public boolean shouldInterceptClassLoading(String className)
183 {
184 return classesToLoad.contains(className);
185 }
186
187 public Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
188 {
189 logger.debug(String.format("BEGIN Analyzing %s", className));
190
191 Class<?> result;
192
193 try
194 {
195 result = doClassLoad(className);
196 } catch (IOException ex)
197 {
198 throw new ClassNotFoundException(String.format("Unable to analyze and load class %s: %s", className,
199 InternalUtils.toMessage(ex)), ex);
200 }
201
202 trackClassFileChanges(className);
203
204 logger.debug(String.format(" END Analyzing %s", className));
205
206 return result;
207 }
208
209 public Class<?> doClassLoad(String className) throws IOException
210 {
211 ClassVisitor analyzer = new ClassVisitor(Opcodes.ASM4)
212 {
213 @Override
214 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
215 {
216 String path = superName + ".class";
217
218 URL url = baseClassLoader.getResource(path);
219
220 if (isFileURL(url))
221 {
222 add(PlasticInternalUtils.toClassName(superName));
223 }
224 }
225
226 @Override
227 public void visitInnerClass(String name, String outerName, String innerName, int access)
228 {
229 // Anonymous inner classes show the outerName as null. Nested classes show the outer name as
230 // the internal name of the containing class.
231 if (outerName == null || classesToLoad.contains(PlasticInternalUtils.toClassName(outerName)))
232 {
233 add(PlasticInternalUtils.toClassName(name));
234 }
235 }
236 };
237
238
239 String path = PlasticInternalUtils.toClassPath(className);
240
241 InputStream stream = baseClassLoader.getResourceAsStream(path);
242
243 assert stream != null;
244
245 ByteArrayOutputStream classBuffer = new ByteArrayOutputStream(5000);
246 byte[] buffer = new byte[5000];
247
248 while (true)
249 {
250 int length = stream.read(buffer);
251
252 if (length < 0)
253 {
254 break;
255 }
256
257 classBuffer.write(buffer, 0, length);
258 }
259
260 stream.close();
261
262 byte[] bytecode = classBuffer.toByteArray();
263
264 new ClassReader(new ByteArrayInputStream(bytecode)).accept(analyzer,
265 ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
266
267
268 return loader.defineClassWithBytecode(className, bytecode);
269 }
270
271 private void trackClassFileChanges(String className)
272 {
273 if (isInnerClassName(className))
274 {
275 return;
276 }
277
278 String path = PlasticInternalUtils.toClassPath(className);
279
280 URL url = baseClassLoader.getResource(path);
281
282 if (isFileURL(url))
283 {
284 changeTracker.add(url);
285 }
286 }
287
288 /**
289 * Returns true if the url is non-null, and is for the "file:" protocol.
290 */
291 private boolean isFileURL(URL url)
292 {
293 return url != null && url.getProtocol().equals("file");
294 }
295
296 private boolean isInnerClassName(String className)
297 {
298 return className.indexOf('$') >= 0;
299 }
300 }