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