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.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
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;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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 */ private static String PLATFORM; static { String jvmName = System.getProperty("java.vm.name", "").toLowerCase(); String osName = System.getProperty("os.name", "").toLowerCase(); String osArch = System.getProperty("os.arch", "").toLowerCase(); String abiType = System.getProperty("sun.arch.abi", "").toLowerCase(); String libPath = System.getProperty("sun.boot.library.path", "").toLowerCase(); if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) { osName = "android"; } else if (jvmName.startsWith("robovm") && osName.startsWith("darwin")) { osName = "ios"; osArch = "arm"; } else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) { osName = "macosx"; } else { int spaceIndex = osName.indexOf(' '); if (spaceIndex > 0) { osName = osName.substring(0, spaceIndex); } } if (osArch.equals("i386") || osArch.equals("i486") || osArch.equals("i586") || osArch.equals("i686")) { osArch = "x86"; } else if (osArch.equals("amd64") || osArch.equals("x86-64") || osArch.equals("x64")) { osArch = "x86_64"; } else if (osArch.startsWith("aarch64") || osArch.startsWith("armv8") || osArch.startsWith("arm64")) { osArch = "arm64"; } else if ((osArch.startsWith("arm")) && ((abiType.equals("gnueabihf")) || (libPath.contains("openjdk-armhf")))) { osArch = "armhf"; } else if (osArch.startsWith("arm")) { osArch = "arm"; } PLATFORM = osName + "-" + osArch; } /** * 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; /** * Filters jar files */ public final static class JarFileFilter implements FilenameFilter { /** * Check whether file matches filter rules * * @param dir * Directory * @param name * File name * @return true If file does match filter rules, false otherwise */ public boolean accept(File dir, String name) { return name.endsWith(".jar"); } } /** * 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_XINS_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). * */ @SuppressWarnings("unused") public static ClassLoader build(File path, int mode, ClassLoader parent) { JarFileFilter jarFileFilter = new JarFileFilter(); List urlList = new ArrayList(31); // 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 String home = System.getProperty("red5.root"); // if home is null check environmental if (home == null) { // check for env variable home = System.getenv("RED5_HOME"); } // if home is null or equal to "current" directory if (home == null || ".".equals(home)) { // if home is still null 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('/')); } try { // add red5.jar to the classpath File red5jar = new File(home, "ant-media-server.jar"); if (!red5jar.exists()) { System.out.println("Ant Media Server jar was not found, using fallback."); red5jar = new File(home, "ant-media-server.jar"); } else { System.out.println("Ant Media 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 String libPath = System.getProperty("red5.lib_root"); if (libPath == null) { // construct the lib path libPath = home + "/lib"; } // System.out.printf("Library path: %s\n", libPath); // try { // // add red5-server-common jar to the classpath // File red5commonjar = new File(home, "red5-server-common.jar"); // if (!red5commonjar.exists()) { // System.out.println("Red5 server common jar was not found"); // } else { // System.out.println("Red5 server common jar was found"); // urlList.add(red5commonjar.toURI().toURL()); // } // } catch (MalformedURLException e1) { // e1.printStackTrace(); // } // grab the urls for all the jars in "lib" File libDir = new File(libPath); // if we are on osx with spaces in our path this may occur if (libDir == null) { libDir = new File(home, "lib"); } File[] libFiles = libDir.listFiles(jarFileFilter); for (File lib : libFiles) { try { urlList.add(lib.toURI().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } // look over the libraries and remove the old versions scrubURLList(urlList); // get config dir String conf = System.getProperty("red5.config_root"); if (conf == null) { conf = home + "/conf"; } // add config dir try { URL confUrl = new File(conf).toURI().toURL(); if (!urlList.contains(confUrl)) { urlList.add(confUrl); } } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } // add the plugins // get red5 lib 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); } // create the directory if it doesnt exist File pluginsDir = new File(pluginsPath); // if we are on osx with spaces in our path this may occur if (pluginsDir == null) { pluginsDir = new File(home, "plugins"); // create the dir pluginsDir.mkdirs(); } // add the plugin directory to the path so that configs // will be resolved and not have to be copied to conf try { URL pluginsUrl = pluginsDir.toURI().toURL(); if (!urlList.contains(pluginsUrl)) { urlList.add(pluginsUrl); } } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } // get all the plugin jars File[] pluginsFiles = pluginsDir.listFiles(jarFileFilter); // this can be null if the dir doesnt exist //pluginsDir.listFiles(filter); loadPlugins(pluginsFiles, urlList, PLATFORM); // 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.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(new File(nextPath).toURI().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } } if (mode == USE_WAR_LIB) { if (path.isDirectory()) { File libDir = new File(path, "WEB-INF/lib"); // this should not be null but it can happen if (libDir != null && libDir.canRead()) { File[] libs = libDir.listFiles(jarFileFilter); // System.out.printf("Webapp lib count: %s\n", // libs.length); for (File lib : libs) { try { urlList.add(lib.toURI().toURL()); } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } } } else { try { JarInputStream jarStream = new JarInputStream(new FileInputStream(path)); 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))) { File 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; } public static void loadPlugins(File[] pluginsFiles, List urlList, String platform) { if (pluginsFiles != null) { for (File plugin : pluginsFiles) { try { String parseUrl = parseUrl(plugin.toURI().toURL()); if (parseUrl.endsWith("x86") || parseUrl.endsWith("x86_64") || parseUrl.endsWith("arm64") || parseUrl.endsWith("armhf") || parseUrl.endsWith("ppc64le") || parseUrl.endsWith("arm")) { if (parseUrl.endsWith(platform)) { urlList.add(plugin.toURI().toURL()); } } else { urlList.add(plugin.toURI().toURL()); } } catch (MalformedURLException e) { System.err.printf("Exception %s\n", e); } } } } /** * 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 File unpack(JarInputStream jarStream, String entryName) throws IOException { String libName = entryName.substring(entryName.lastIndexOf('/') + 1, entryName.length() - 4); File tempJarFile = File.createTempFile("tmp_" + libName, ".jar"); tempJarFile.deleteOnExit(); FileOutputStream out = new FileOutputStream(tempJarFile); // Transfer bytes from the JAR file to the output file byte[] buf = new byte[8192]; int len; while ((len = jarStream.read(buf)) > 0) { out.write(buf, 0, len); } out.close(); return tempJarFile; } /** * Removes older versions of libraries from a given list based on their version numbers. * * @param list */ private final static void scrubURLList(List 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(); // 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