/* * Copyright (C) 2009-2013 moparisthebest * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * Official forums are http://www.moparscape.org/smf/ * Email me at admin@moparisthebest.com. */ package org.moparscape.classloader; import org.moparscape.Debug; import org.moparscape.res.impl.Downloader; import java.io.*; import java.security.ProtectionDomain; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.zip.CRC32; import java.util.zip.CheckedOutputStream; import java.util.zip.GZIPInputStream; /** * This is a class loader that loads classes from jars on the filesystem, optionally checking the CRC of the classes and * grabbing new updated jars if the CRC doesn't match. *

* todo: make this extend URLClassLoader so resources etc are handled automatically */ public class CRCClassLoaderOrig extends ClassLoader { private Map classes = new HashMap(); private long crcVal = 0; private boolean fileExists = false; private ClassLoader parent = null; private ProtectionDomain pd = null; private int classesLoaded = 0; /** * Reads the jar file and calculates the CRC. Sets up the class. * Responsibility of checking the CRC is the user's * * @param jarFileLoc The location of the jar file to load on the disk */ public CRCClassLoaderOrig(String jarFileLoc) throws IOException { this(jarFileLoc, null); } public CRCClassLoaderOrig(String jarFileLoc, Class parent) throws IOException { //super(parent == null ? getSystemClassLoader() : parent.getClassLoader()); setup(jarFileLoc); if (parent != null) { this.parent = parent.getClassLoader(); this.pd = parent.getProtectionDomain(); } } public static CRCClassLoaderOrig newInstance(String jarFileLoc, String backupURL, long expectedCRC, boolean crcMismatchException) throws IOException { return newInstance(jarFileLoc, backupURL, expectedCRC, null, crcMismatchException); } /** * Reads the jar file and calculates the CRC. Sets up the class. * If the provided CRC does not match, downloads the new jar file to * the provided location and tries again. If it fails again, an IOException is thrown. * * @param jarFileLoc The location of the jar file to load on the disk */ public static CRCClassLoaderOrig newInstance(String jarFileLoc, String backupURL, long expectedCRC, Class parent, boolean crcMismatchException) throws IOException { CRCClassLoaderOrig ret = null; // wrap this one in a try / catch, so we can retry if it throws an exception try { ret = new CRCClassLoaderOrig(jarFileLoc, parent); // check CRC if (ret.successfullyLoaded(expectedCRC)) return ret; } catch (IOException e) { //e.printStackTrace(); // if we don't have a backupURL, just go ahead and rethrow this exception, can't do anything more if (backupURL == null) throw e; } if (backupURL != null) { //if(ret == null || ret.getCRC() != 0) if (ret != null && ret.fileExists()) System.out.println("CRC checksum failed, downloading new file."); // URLConnection uc = new URL(backupURL).openConnection(); // InputStream in = uc.getInputStream(); // FileOutputStream fos = new FileOutputStream(jarFileLoc); // byte[] buffer = new byte[1024]; // int len; // while ((len = in.read(buffer)) >= 0) // fos.write(buffer, 0, len); // fos.flush(); // in.close(); // fos.close(); // use Update instead //new Update(backupURL, jarFileLoc, true); // use ResourceGrabber int jarWait = org.moparscape.res.ResourceGrabber.getRG().download(backupURL, jarFileLoc); if (org.moparscape.res.ResourceGrabber.getRG().waitCatch(jarWait)) { jarFileLoc = org.moparscape.res.ResourceGrabber.getRG().firstFileEndsWithIgnoreCase(jarWait, "jar.gz", "jar"); org.moparscape.res.ResourceGrabber.getRG().freeResources(jarWait); } Debug.debug("new jarFileLoc: " + jarFileLoc); ret = new CRCClassLoaderOrig(jarFileLoc, parent); } if (!ret.successfullyLoaded(expectedCRC) && ret.getCRC() != 0) { String s = "CRC checksum failed. crc:" + ret.getCRC() + " expected:" + expectedCRC; if (crcMismatchException) throw new IOException(s); else System.err.println(s); } return ret; } public void addJar(String jarFileLoc) throws IOException { this.setup(jarFileLoc, false); } private void setup(String jarFileLoc) throws IOException { this.setup(jarFileLoc, true); } public boolean successfullyLoaded(long expectedCRC) { if (expectedCRC == 0) return classesLoaded > 0; else return crcVal == expectedCRC; //return (expectedCRC == 0 && classesLoaded > 0) || crcVal == expectedCRC; } public boolean fileExists() { return this.fileExists; } private void setup(String jarFileLoc, boolean updateCRC) throws IOException { File f = new File(jarFileLoc); fileExists = f.exists() && f.isFile() && f.canRead(); if (!fileExists) { Debug.debug("Jar file doesn't exist: " + jarFileLoc); return; } Debug.debug("Loading classes from jar: " + f.getAbsolutePath()); if (updateCRC) classes = new HashMap(); CRC32 crc = null; if (updateCRC) { crcVal = 0; crc = new CRC32(); } /* JarFile jf = new JarFile(f); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = (JarEntry) entries.nextElement(); */ InputStream is = new FileInputStream(f); if (jarFileLoc.toLowerCase().endsWith(".gz")) is = new GZIPInputStream(is); JarInputStream in = new JarInputStream(is); JarEntry entry; while ((entry = in.getNextJarEntry()) != null) { if (entry.getName().endsWith(".class")) { //InputStream in = jf.getInputStream(entry); ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream out = baos; if (updateCRC) //in = new CheckedInputStream(in, crc); out = new CheckedOutputStream(out, crc); Downloader.writeStream(in, out); String className = entry.getName().substring(0, entry.getName().lastIndexOf(".")).replaceAll("/", "."); //if (updateCRC) System.out.println("class name: " + className); // save class classes.put(className, baos.toByteArray()); ++classesLoaded; } } //jf.close(); in.close(); if (updateCRC) crcVal = crc.getValue(); } public long getCRC() { return crcVal; } @Override public String toString() { return "CRCClassLoaderOrig{" + "crcVal=" + crcVal + ", classesLoaded=" + classesLoaded + ", fileExists=" + fileExists + //", successfullyLoaded(0)=" + successfullyLoaded(0) + '}'; } /** * Is called by the ClassLoader when the requested class * is not found in its cache. The parent is only used when * this is ran as an applet, strangely. * * @param name The name of the class */ public Class findClass(String name) throws ClassNotFoundException { System.out.println("CRCClassLoader: Requesting class '" + name + "'"); byte[] classBytes = classes.get(name); if (classBytes == null) { /**/ if (parent == null) throw new ClassNotFoundException("Couldn't find class " + name); //System.out.println("Couldn't find class '" + name + "' trying parent class loader."); return parent.loadClass(name); } Class foundClass = defineClass(name, classBytes, 0, classBytes.length, pd); return foundClass; } }