All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.classloading.ClassLoaderBuilder Maven / Gradle / Ivy

/*
 * RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2016 by respective authors (see below). All rights reserved. Licensed under the Apache License, Version
 * 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless
 * required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

package org.red5.classloading;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;

/**
 * Class used to get the Servlet Class loader. The class loader returned is a child first class loader.
 *
 * 
* This class is based on original code from the XINS project, by Anthony Goubard ([email protected]) * * @author Paul Gregoire ([email protected]) */ public final class ClassLoaderBuilder { /* * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6500212 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6516909 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4976356 */ /** * Load the Servlet code from the WAR file and use the current classpath for the libraries. */ public static final int USE_CLASSPATH_LIB = 1; /** * Load the servlet code from the WAR file and try to find the libraries in the common red5 lib directory. */ public static final int USE_RED5_LIB = 2; /** * Load the servlet code and the libraries from the WAR file. This may take some time as the libraries need to be extracted from the WAR * file. */ public static final int USE_WAR_LIB = 3; /** * Default build uses Red5 common lib without a parent classloader. * * @return the class loader */ public static ClassLoader build() { return ClassLoaderBuilder.build(null, USE_RED5_LIB, null); } /** * Gets a class loader based on mode. * * @param path * the directory or file containing classes * @param mode * the mode in which the servlet should be loaded. The possible values are * *
     * USE_CURRENT_CLASSPATH, USE_CLASSPATH_LIB, USE_WAR_LIB
     * 
* @param parent * the parent class loader or null if you want the current threads class loader * @return the Class loader to use to load the required class(es) */ public static ClassLoader build(Path path, int mode, ClassLoader parent) { final Set urlList = new HashSet<>(); // the class loader to return ClassLoader loader = null; // urls to load resources / classes from URL[] urls = null; if (mode == USE_RED5_LIB) { // get red5 home // look for red5 home as a system property Path homeDir = null; String home = System.getProperty("red5.root", System.getenv("RED5_HOME")); // if home is null check environmental if (home != null) { homeDir = Paths.get(home); } else { // if home is null or equal to "current" directory, look it up via this classes loader String classLocation = ClassLoaderBuilder.class.getProtectionDomain().getCodeSource().getLocation().toString(); // System.out.printf("Classloader location: %s\n", classLocation); // snip off anything beyond the last slash home = classLocation.substring(0, classLocation.lastIndexOf('/')); homeDir = Paths.get(home); } try { // add red5.jar to the classpath Path red5jar = homeDir.resolve("red5-server.jar"); if (!Files.exists(red5jar)) { System.out.println("Red5 server jar was not found, using fallback"); red5jar = homeDir.resolve("red5.jar"); } else { System.out.println("Red5 server jar was found"); } urlList.add(red5jar.toUri().toURL()); } catch (MalformedURLException e1) { e1.printStackTrace(); } System.out.printf("URL list: %s\n", urlList); // get red5 lib system property, if not found build it Path libDir = null; String lib = System.getProperty("red5.lib_root"); if (lib != null) { libDir = Paths.get(lib); } else { libDir = homeDir.resolve("lib"); } try { // add lib dir //urlList.add(libDir.toUri().toURL()); // get all the lib jars Files.walkFileTree(libDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { //System.out.printf("Lib file: %s%n", file.toAbsolutePath()); if (file.toFile().getName().endsWith(".jar")) { try { urlList.add(file.toUri().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } return FileVisitResult.CONTINUE; } }); } catch (Exception e) { System.err.printf("Exception %s\n", e); } // look over the libraries and remove the old versions scrubURLList(urlList); // get config dir Path confDir = null; String conf = System.getProperty("red5.config_root"); if (conf != null) { confDir = Paths.get(conf); } else { confDir = homeDir.resolve("conf"); } // add config dir try { urlList.add(confDir.toUri().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } // get red5 plugins system property, if not found build it String pluginsPath = System.getProperty("red5.plugins_root"); if (pluginsPath == null) { // construct the plugins path pluginsPath = home + "/plugins"; // update the property System.setProperty("red5.plugins_root", pluginsPath); } try { // create the directory if it doesnt exist Path pluginsDir = Files.createDirectories(Paths.get(pluginsPath)); // add the plugin directory to the path so that configs will be resolved and not have to be copied to conf urlList.add(pluginsDir.toUri().toURL()); // get all the plugin jars Files.walkFileTree(pluginsDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toFile().getName().endsWith(".jar")) { try { urlList.add(file.toUri().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } return FileVisitResult.CONTINUE; } }); } catch (Exception e) { System.err.printf("Exception %s\n", e); } // create the url array that the classloader wants urls = urlList.toArray(new URL[0]); //System.out.printf("Selected libraries: (%s items)\n", urls.length); //for (URL url : urls) { // System.out.println(url); //} //System.out.println(); // instance a url classloader using the selected jars if (parent == null) { loader = new URLClassLoader(urls); } else { loader = new URLClassLoader(urls, parent); } } else { List standardLibs = new ArrayList(7); if (path != null) { try { urlList.add(path.toUri().toURL()); URL classesURL = new URL("jar:file:" + path.toFile().getAbsolutePath().replace(File.separatorChar, '/') + "!/WEB-INF/classes/"); urlList.add(classesURL); } catch (MalformedURLException e1) { e1.printStackTrace(); } } if (mode == USE_CLASSPATH_LIB) { String classPath = System.getProperty("java.class.path"); StringTokenizer stClassPath = new StringTokenizer(classPath, File.pathSeparator); while (stClassPath.hasMoreTokens()) { String nextPath = stClassPath.nextToken(); if (nextPath.toLowerCase().endsWith(".jar")) { standardLibs.add(nextPath.substring(nextPath.lastIndexOf(File.separatorChar) + 1)); } try { urlList.add(Paths.get(nextPath).toUri().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } } if (mode == USE_WAR_LIB) { if (path.toFile().isDirectory()) { Path libDir = path.resolve("WEB-INF").resolve("lib"); try { Files.walkFileTree(libDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toFile().getName().endsWith(".jar")) { try { urlList.add(file.toUri().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { System.err.printf("Exception %s\n", e); } } else { try (InputStream is = Files.newInputStream(path); JarInputStream jarStream = new JarInputStream(is)) { JarEntry entry = jarStream.getNextJarEntry(); while (entry != null) { String entryName = entry.getName(); if (entryName.startsWith("WEB-INF/lib/") && entryName.endsWith(".jar") && !standardLibs.contains(entryName.substring(12))) { Path tempJarFile = unpack(jarStream, entryName); urlList.add(tempJarFile.toUri().toURL()); } entry = jarStream.getNextJarEntry(); } jarStream.close(); } catch (IOException e) { System.err.printf("Exception %s\n", e); } } } urls = urlList.toArray(new URL[0]); loader = new ChildFirstClassLoader(urls, parent); } Thread.currentThread().setContextClassLoader(loader); // loop thru all the current urls // System.out.printf("Classpath for %s:\n", loader); // for (URL url : urls) { // System.out.println(url.toExternalForm()); // } return loader; } /** * Unpack the specified entry from the JAR file. * * @param jarStream * The input stream of the JAR file positioned at the entry * @param entryName * The name of the entry to extract * @return The extracted file. The created file is a temporary file in the temporary directory * @throws IOException * if the JAR file cannot be read or is incorrect */ private static Path unpack(JarInputStream jarStream, String entryName) throws IOException { String libName = entryName.substring(entryName.lastIndexOf('/') + 1, entryName.length() - 4); Path tempJarFile = Files.createTempFile("tmp_" + libName, ".jar"); try (OutputStream os = Files.newOutputStream(tempJarFile)) { // Transfer bytes from the JAR file to the output file byte[] buf = new byte[1024]; int len; while ((len = jarStream.read(buf)) > 0) { os.write(buf, 0, len); } } return tempJarFile; } /** * Removes older versions of libraries from a given list based on their version numbers. * * @param list */ private final static void scrubURLList(Collection list) { String ALPHABET = "abcdefghijklmnopqrstuvwxyz"; Pattern punct = Pattern.compile("\\p{Punct}"); Set removalList = new HashSet(list.size()); String topName = null; String checkName = null; URL[] urls = list.toArray(new URL[0]); // System.out.printf("Library list: (%s items)\n", urls.length); // for (URL url : urls) { // System.out.println(url); // } // System.out.println(); for (URL top : urls) { if (removalList.contains(top)) { continue; } topName = parseUrl(top); // empty name - this happens inside eclipse if ("".equals(topName)) { removalList.add(top); continue; } // skip red5 if (topName.startsWith("red5")) { continue; } // skip version-less libraries if (topName.endsWith("-")) { removalList.add(top); continue; } // by default we will get rid of testing libraries if (topName.startsWith("grobo") || topName.startsWith("junit") || topName.startsWith("ivy")) { removalList.add(top); continue; } // by default we will get rid of "javadoc" and "sources" jars if (topName.contains("javadoc") || topName.contains("sources")) { removalList.add(top); continue; } int topFirstDash = topName.indexOf('-'); // if theres no dash then just grab the first 3 chars // FIXME: why // just grab the first 3 characters? String prefix = topName.substring(0, topFirstDash != -1 ? topFirstDash : 3); int topSecondDash = topName.indexOf('-', topFirstDash + 1); for (URL check : list) { if (removalList.contains(check)) { continue; } checkName = parseUrl(check); // if its the same lib just continue with the next if (checkName.equals(topName)) { continue; } // if the last character is a dash then skip it if (checkName.endsWith("-")) { continue; } // check starts with to see if we should do version check if (!checkName.startsWith(prefix)) { continue; } // check for next dash if (topSecondDash > 0) { if (checkName.length() <= topSecondDash) { continue; } // check for second dash in check lib at same position if (checkName.charAt(topSecondDash) != '-') { continue; } // split the names String[] topSubs = topName.split("-"); String[] checkSubs = checkName.split("-"); // check lib type "spring-aop" vs "spring-orm" if (!topSubs[1].equals(checkSubs[1])) { continue; } // see if next entry is a number if (!Character.isDigit(topSubs[2].charAt(0)) && !Character.isDigit(checkSubs[2].charAt(0))) { // check next lib name section for a match if (!topSubs[2].equals(checkSubs[2])) { continue; } } } // do the version check // read from end to get version info // System.out.printf("Read from end to get version info: %s top 1st dash: %d top 2nd dash: %d\n", // checkName, topFirstDash, topSecondDash); if (checkName.length() < (topFirstDash + 1)) { continue; } String checkVers = checkName.substring(topSecondDash != -1 ? (topSecondDash + 1) : (topFirstDash + 1)); if (checkVers.startsWith("-")) { continue; } // get top libs version info String topVers = topName.substring(topSecondDash != -1 ? (topSecondDash + 1) : (topFirstDash + 1)); int topThirdDash = -1; String topThirdName = null; if (topVers.length() > 0 && !Character.isDigit(topVers.charAt(0))) { // check if third level lib name matches topThirdDash = topVers.indexOf('-'); // no version most likely exists if (topThirdDash == -1) { continue; } topThirdName = topVers.substring(0, topThirdDash); topVers = topVers.substring(topThirdDash + 1); } // if check version starts with a non-number skip it int checkThirdDash = -1; String checkThirdName = null; if (!Character.isDigit(checkVers.charAt(0))) { // check if third level lib name matches checkThirdDash = checkVers.indexOf('-'); // no version most likely exists if (checkThirdDash == -1) { continue; } checkThirdName = checkVers.substring(0, checkThirdDash); if (topThirdName == null || !topThirdName.equals(checkThirdName)) { continue; } checkVers = checkVers.substring(checkThirdDash + 1); // if not if (!Character.isDigit(checkVers.charAt(0))) { continue; } } if (topThirdName != null && checkThirdName == null) { continue; } // check major String[] topVersion = punct.split(topVers); // System.out.println("topVersion (" + topVers + "): " + // topVersion[0] + " length: " + topVersion.length); if (!topVersion[0].matches("[\\d].*")) { continue; } // check 3rd part of version for letters if (topVersion.length > 2) { String v = topVersion[2].toLowerCase(); if (v.length() > 1) { topVersion[2] = deleteAny(v, ALPHABET); } // after alpha removal, string is any digits or single char if (topVersion[2].length() == 1) { // if is a only a letter use its index as a version char ch = v.charAt(0); if (!Character.isDigit(ch)) { topVersion[2] = ALPHABET.indexOf(ch) + ""; } } } // System.out.println("AOB " + checkVers + " | " + topVersion[0] // + " length: " + topVersion.length); int topVersionNumber; try { topVersionNumber = topVersion.length == 1 ? Integer.valueOf(topVersion[0]) : Integer.valueOf(topVersion[0] + topVersion[1] + (topVersion.length > 2 ? topVersion[2] : '0')).intValue(); } catch (NumberFormatException nfe) { topVersionNumber = 0; System.err.println("Error parsing topVers:" + topVers); } String[] checkVersion = punct.split(checkVers); // System.out.println("checkVersion (" + checkVers + "): " + // checkVersion[0] + " length: " + checkVersion.length); // check 3rd part of version for letters if (checkVersion.length > 2) { String v = checkVersion[2].toLowerCase(); if (v.length() > 1) { checkVersion[2] = deleteAny(v, ALPHABET); } // after alpha removal, string is any digits or single char if (checkVersion[2].length() == 1) { // if is a only a letter use its index as a version char ch = v.charAt(0); if (!Character.isDigit(ch)) { checkVersion[2] = ALPHABET.indexOf(ch) + ""; } } } int checkVersionNumber; try { checkVersionNumber = checkVersion.length == 1 ? Integer.valueOf(checkVersion[0]) : Integer.valueOf(checkVersion[0] + checkVersion[1] + (checkVersion.length > 2 ? checkVersion[2] : '0')).intValue(); } catch (NumberFormatException nfe) { checkVersionNumber = 0; System.err.println("Error parsing checkVers:" + checkVers); } // Check version numbers if (topVersionNumber >= checkVersionNumber) { // remove it removalList.add(check); } else { removalList.add(top); break; } } } // remove the old libs // System.out.println("Removal list:"); // for (URL url : removalList) { // System.out.println(url); // } list.removeAll(removalList); } /** * Parses url and returns the jar filename stripped of the ending .jar * * @param url * @return */ private static String parseUrl(URL url) { String external = url.toExternalForm().toLowerCase(); //System.out.printf("parseUrl %s%n", external); // get everything after the last slash String[] parts = external.split("/"); // last part String libName = parts[parts.length - 1]; // strip .jar libName = libName.substring(0, libName.length() - 4); return libName; } private static String deleteAny(String str, String removalChars) { StringBuilder sb = new StringBuilder(str); // System.out.println("Before alpha delete: " + sb.toString()); String[] chars = removalChars.split(""); // System.out.println("Chars length: " + chars.length); for (String c : chars) { int index = -1; while ((index = sb.indexOf(c)) > 0) { sb.deleteCharAt(index); } } // System.out.println("After alpha delete: " + sb.toString()); return sb.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy