1 7 package winstone.classLoader; 8 9 import java.io.File ; 10 import java.io.IOException ; 11 import java.net.URL ; 12 import java.net.URLClassLoader ; 13 import java.util.Date ; 14 import java.util.Enumeration ; 15 import java.util.HashMap ; 16 import java.util.HashSet ; 17 import java.util.Map ; 18 import java.util.Set ; 19 import java.util.jar.JarEntry ; 20 import java.util.jar.JarFile ; 21 22 import javax.servlet.ServletContextEvent ; 23 import javax.servlet.ServletContextListener ; 24 25 import winstone.Logger; 26 import winstone.WebAppConfiguration; 27 import winstone.WinstoneResourceBundle; 28 29 37 public class ReloadingClassLoader extends URLClassLoader implements ServletContextListener , Runnable { 38 private static final int RELOAD_SEARCH_SLEEP = 50; 39 private static final WinstoneResourceBundle CL_RESOURCES = new WinstoneResourceBundle("winstone.classLoader.LocalStrings"); 40 private boolean interrupted; 41 private WebAppConfiguration webAppConfig; 42 private Set loadedClasses; 43 private File classPaths[]; 44 private int classPathsLength; 45 46 public ReloadingClassLoader(URL urls[], ClassLoader parent) { 47 super(urls, parent); 48 this.loadedClasses = new HashSet (); 49 if (urls != null) { 50 this.classPaths = new File [urls.length]; 51 for (int n = 0 ; n < urls.length; n++) { 52 this.classPaths[this.classPathsLength++] = new File (urls[n].getFile()); 53 } 54 } 55 } 56 57 protected void addURL(URL url) { 58 super.addURL(url); 59 synchronized (this.loadedClasses) { 60 if (this.classPaths == null) { 61 this.classPaths = new File [10]; 62 this.classPathsLength = 0; 63 } else if (this.classPathsLength == (this.classPaths.length - 1)) { 64 File temp[] = this.classPaths; 65 this.classPaths = new File [(int) (this.classPathsLength * 1.75)]; 66 System.arraycopy(temp, 0, this.classPaths, 0, this.classPathsLength); 67 } 68 this.classPaths[this.classPathsLength++] = new File (url.getFile()); 69 } 70 } 71 72 public void contextInitialized(ServletContextEvent sce) { 73 this.webAppConfig = (WebAppConfiguration) sce.getServletContext(); 74 this.interrupted = false; 75 synchronized (this) { 76 this.loadedClasses.clear(); 77 } 78 Thread thread = new Thread (this, CL_RESOURCES 79 .getString("ReloadingClassLoader.ThreadName")); 80 thread.setDaemon(true); 81 thread.setPriority(Thread.MIN_PRIORITY); 82 thread.start(); 83 } 84 85 public void contextDestroyed(ServletContextEvent sce) { 86 this.interrupted = true; 87 this.webAppConfig = null; 88 synchronized (this) { 89 this.loadedClasses.clear(); 90 } 91 } 92 93 97 public void run() { 98 Logger.log(Logger.FULL_DEBUG, CL_RESOURCES, 99 "ReloadingClassLoader.MaintenanceThreadStarted"); 100 101 Map classDateTable = new HashMap (); 102 Map classLocationTable = new HashMap (); 103 Set lostClasses = new HashSet (); 104 while (!interrupted) { 105 try { 106 Thread.sleep(RELOAD_SEARCH_SLEEP); 107 String loadedClassesCopy[] = null; 108 synchronized (this) { 109 loadedClassesCopy = (String []) this.loadedClasses.toArray(new String [0]); 110 } 111 112 for (int n = 0; (n < loadedClassesCopy.length) && !interrupted; n++) { 113 String className = transformToFileFormat(loadedClassesCopy[n]); 114 File location = (File ) classLocationTable.get(className); 115 Long classDate = null; 116 if ((location == null) || !location.exists()) { 117 for (int j = 0; (j < this.classPaths.length) && (classDate == null); j++) { 118 File path = this.classPaths[j]; 119 if (!path.exists()) { 120 continue; 121 } else if (path.isDirectory()) { 122 File classLocation = new File (path, className); 123 if (classLocation.exists()) { 124 classDate = new Long (classLocation.lastModified()); 125 classLocationTable.put(className, classLocation); 126 } 127 } else if (path.isFile()) { 128 classDate = searchJarPath(className, path); 129 if (classDate != null) 130 classLocationTable.put(className, path); 131 } 132 } 133 } else if (location.exists()) 134 classDate = new Long (location.lastModified()); 135 136 if (classDate == null) { 138 if (!lostClasses.contains(className)) { 139 lostClasses.add(className); 140 Logger.log(Logger.DEBUG, CL_RESOURCES, 141 "ReloadingClassLoader.ClassLost", className); 142 } 143 continue; 144 } 145 if ((classDate != null) && lostClasses.contains(className)) { 146 lostClasses.remove(className); 147 } 148 149 Long oldClassDate = (Long ) classDateTable.get(className); 152 if (oldClassDate == null) { 153 classDateTable.put(className, classDate); 154 } else if (oldClassDate.compareTo(classDate) != 0) { 155 Logger.log(Logger.INFO, CL_RESOURCES, 157 "ReloadingClassLoader.ReloadRequired", 158 new String [] {className, 159 "" + new Date (classDate.longValue()), 160 "" + new Date (oldClassDate.longValue()) }); 161 this.webAppConfig.resetClassLoader(); 162 } 163 } 164 } catch (Throwable err) { 165 Logger.log(Logger.ERROR, CL_RESOURCES, 166 "ReloadingClassLoader.MaintenanceThreadError", err); 167 } 168 } 169 Logger.log(Logger.FULL_DEBUG, CL_RESOURCES, 170 "ReloadingClassLoader.MaintenanceThreadFinished"); 171 } 172 173 protected Class findClass(String name) throws ClassNotFoundException { 174 synchronized (this) { 175 this.loadedClasses.add("Class:" + name); 176 } 177 return super.findClass(name); 178 } 179 180 public URL findResource(String name) { 181 synchronized (this) { 182 this.loadedClasses.add(name); 183 } 184 return super.findResource(name); 185 } 186 187 190 private Long searchJarPath(String classResourceName, File path) 191 throws IOException , InterruptedException { 192 JarFile jar = new JarFile (path); 193 for (Enumeration e = jar.entries(); e.hasMoreElements() && !interrupted;) { 194 JarEntry entry = (JarEntry ) e.nextElement(); 195 if (entry.getName().equals(classResourceName)) 196 return new Long (path.lastModified()); 197 } 198 return null; 199 } 200 201 private static String transformToFileFormat(String name) { 202 if (!name.startsWith("Class:")) 203 return name; 204 else 205 return WinstoneResourceBundle.globalReplace(name.substring(6), ".", "/") + ".class"; 206 } 207 } 208 | Popular Tags |