Java tutorial
/******************************************************************************* * Copyright (c) 2013, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.team.build.internal.hjplugin; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.collections.map.LRUMap; /** * Factory for the RTC Build client. */ public class RTCFacadeFactory { private static final Logger LOGGER = Logger.getLogger(RTCFacadeFactory.class.getName()); private static final int DEFAULT_CACHE_SIZE = 10; private static final String CACHE_SIZE_PROPERTY = "com.ibm.team.build.classLoaderCacheSize"; //$NON-NLS-1$ private static final String DISABLE_RTC_FACADE_CLASS_LOADER_PROPERTY = "com.ibm.team.build.disableRTCFacadeClassLoader"; //$NON-NLS-1$ private static transient LRUMap fgRTCFacadeCache; private static transient URL fgHJPlugin_rtcJar; /** * Returns a facade for interfacing with RTC, using the classes in the RTC build toolkit at the given path. * Current limitation: only one toolkit path at a time is supported. * This will create a new facade if the path changes, but that's expensive. * Note that the return type is a wrapper and only supports the invoke reflection method. This is * deliberate since RTCFacade must be loaded with the class loader that can see the RTC/Jazz/Eclipse types * and it will manage the class loader when executing methods. */ public synchronized static RTCFacadeWrapper getFacade(String buildToolkitPath, PrintStream debugLog) throws Exception { if (fgRTCFacadeCache == null) { int mapSize = DEFAULT_CACHE_SIZE; String mapSizeProperty = System.getProperty(CACHE_SIZE_PROPERTY, String.valueOf(DEFAULT_CACHE_SIZE)); try { mapSize = Integer.parseInt(mapSizeProperty); } catch (NumberFormatException e) { debug(debugLog, "Unable to parse system property " + CACHE_SIZE_PROPERTY + "=" + mapSizeProperty); //$NON-NLS-1$//$NON-NLS-2$ } debug(debugLog, "Class loader cache size is " + mapSize); //$NON-NLS-1$ fgRTCFacadeCache = new LRUMap(mapSize); } if (buildToolkitPath == null) { throw new IllegalArgumentException(Messages.RTCFacadeFactory_missing_toolkit()); } File buildToolkitFile = new File(buildToolkitPath); String stdBuildToolkitPath = buildToolkitFile.getAbsolutePath(); RTCFacadeWrapper rtcFacade = (RTCFacadeWrapper) fgRTCFacadeCache.get(stdBuildToolkitPath); if (rtcFacade == null) { rtcFacade = RTCFacadeFactory.newFacade("com.ibm.team.build.internal.hjplugin.rtc.RTCFacade", //$NON-NLS-1$ buildToolkitFile, debugLog); if (fgRTCFacadeCache.isFull()) { debug(debugLog, "Class loader cache(" + fgRTCFacadeCache.maxSize() + ") is full."); //$NON-NLS-1$ //$NON-NLS-2$ } fgRTCFacadeCache.put(stdBuildToolkitPath, rtcFacade); // only cache if successful } else { debug(debugLog, "Reusing facade for " + stdBuildToolkitPath); //$NON-NLS-1$ } return rtcFacade; } /** * @return The URL for the jar containing the facade. */ public synchronized static URL getFacadeJarURL(PrintStream debugLog) { // if we already have a cached facade for the toolkit requested, get the path from there. // otherwise find it without creating a facade. if (fgHJPlugin_rtcJar != null) { return fgHJPlugin_rtcJar; } // Find the jar from our class loader Class<?> originalClass = RTCFacadeFactory.class; ClassLoader originalClassLoader = originalClass.getClassLoader(); debug(debugLog, "Original class loader: " + originalClassLoader); //$NON-NLS-1$ // Get the jar for the hjplugin-rtc jar. fgHJPlugin_rtcJar = getHjplugin_rtcJar(originalClassLoader, "com.ibm.team.build.internal.hjplugin.rtc.RTCFacade", debugLog); //$NON-NLS-1$ return fgHJPlugin_rtcJar; } /** * Wrapper on the facade to ensure the class loader is setup and restored * between calls */ public static class RTCFacadeWrapper { private Object facade; private ClassLoader newClassLoader; public Object invoke(String methodName, Class[] argumentTypes, Object... arguments) throws Exception { ClassLoader currentClassLoader = setContextClassLoader(); try { Method m = facade.getClass().getMethod(methodName, argumentTypes); return m.invoke(facade, arguments); } catch (NoSuchMethodException e) { LOGGER.finer(e.getMessage()); StringBuilder lookingFor = new StringBuilder("Looking for: "); //$NON-NLS-1$ lookingFor.append(methodName).append("("); //$NON-NLS-1$ boolean first = true; for (Class argument : argumentTypes) { if (!first) { lookingFor.append(","); //$NON-NLS-1$ } else { first = false; } lookingFor.append(argument.getSimpleName()); } lookingFor.append(")"); //$NON-NLS-1$ LOGGER.finer(lookingFor.toString()); for (Method method : facade.getClass().getMethods()) { LOGGER.finer(method.toString()); } throw e; } catch (InvocationTargetException e) { Throwable eToReport = e.getCause(); if (eToReport instanceof Exception) { throw (Exception) eToReport; } else { throw e; } } finally { resetContextClassLoader(currentClassLoader); } } /** * Sets the current thread's context class loader to be ours. This is needed to ensure that * the EMF package registry can find the previously registered packages. * Without this, you'll likely see EMF package not found errors. */ protected ClassLoader setContextClassLoader() { ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); return originalClassLoader; } protected void resetContextClassLoader(ClassLoader classLoader) { Thread.currentThread().setContextClassLoader(classLoader); } } public static RTCFacadeWrapper newTestingFacade(String toolkitPath) throws Exception { return newFacade("com.ibm.team.build.internal.hjplugin.rtc.tests.RTCTestingFacade", new File(toolkitPath), //$NON-NLS-1$ null); } private static RTCFacadeWrapper newFacade(String fullClassName, File toolkitFile, PrintStream debugLog) throws Exception { if (!toolkitFile.exists()) { throw new IllegalArgumentException( Messages.RTCFacadeFactory_toolkit_not_found(toolkitFile.getAbsolutePath())); } if (!toolkitFile.isDirectory()) { throw new IllegalArgumentException( Messages.RTCFacadeFactory_toolkit_path_not_directory(toolkitFile.getAbsolutePath())); } RTCFacadeWrapper result = new RTCFacadeWrapper(); URL[] toolkitURLs = getToolkitJarURLs(toolkitFile, debugLog); Class<?> originalClass = RTCFacadeFactory.class; ClassLoader originalClassLoader = originalClass.getClassLoader(); debug(debugLog, "Original class loader: " + originalClassLoader); //$NON-NLS-1$ // Get the jar for the hjplugin-rtc jar. URL[] combinedURLs; URL hjplugin_rtcJar = getFacadeJarURL(debugLog); if (hjplugin_rtcJar != null) { combinedURLs = new URL[toolkitURLs.length + 1]; combinedURLs[0] = hjplugin_rtcJar; System.arraycopy(toolkitURLs, 0, combinedURLs, 1, toolkitURLs.length); } else { combinedURLs = toolkitURLs; } debug(debugLog, "System class loader " + ClassLoader.getSystemClassLoader()); //$NON-NLS-1$ // We want the parent class loader to exclude the class loader which would normally // load classes from the hjplugin-rtc jar (because that class loader doesn't include // the toolkit jars). ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader(); // Normally the system class loader and the original class loader are different. // However in the case of running the tests within our build, the system class loader // is the original class loader with our single jar (and not its dependencies from the // toolkit) which results in ClassNotFound for classes we depend on (i.e. IProgressMonitor). // So use the parent in this case. if (parentClassLoader == originalClassLoader) { debug(debugLog, "System class loader and original are the same. Using parent " //$NON-NLS-1$ + originalClassLoader.getParent()); parentClassLoader = originalClassLoader.getParent(); } if (Boolean.parseBoolean(System.getProperty(DISABLE_RTC_FACADE_CLASS_LOADER_PROPERTY, "false"))) { //$NON-NLS-1$ debug(debugLog, "RTCFacadeClassLoader disabled, using URLClassLoader"); //$NON-NLS-1$ result.newClassLoader = new URLClassLoader(combinedURLs, parentClassLoader); } else { result.newClassLoader = new RTCFacadeClassLoader(combinedURLs, parentClassLoader); } debug(debugLog, "new classloader: " + result.newClassLoader); //$NON-NLS-1$ Class<?> facadeClass = result.newClassLoader.loadClass(fullClassName); debug(debugLog, "facadeClass: " + facadeClass); //$NON-NLS-1$ debug(debugLog, "facadeClass classloader: " + facadeClass.getClassLoader()); //$NON-NLS-1$ // using the new class loader get the facade instance // then revert immediately back to the original class loader ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(result.newClassLoader); try { result.facade = facadeClass.newInstance(); } finally { Thread.currentThread().setContextClassLoader(originalContextClassLoader); } debug(debugLog, "facade: " + result.facade); //$NON-NLS-1$ return result; } private static URL getHjplugin_rtcJar(ClassLoader originalClassLoader, String fullClassName, PrintStream debugLog) { if (originalClassLoader instanceof URLClassLoader) { URLClassLoader urlClassLoader = (URLClassLoader) originalClassLoader; URL[] originalURLs = urlClassLoader.getURLs(); for (URL url : originalURLs) { String file = url.getFile(); if (file.contains("com.ibm.team.build.hjplugin-rtc")) { //$NON-NLS-1$ //$NON-NLS-2$ debug(debugLog, "Found hjplugin-rtc jar " + url.getFile()); //$NON-NLS-1$ return url; } } debug(debugLog, "Did not find hjplugin-rtc jar from URLClassLoader"); //$NON-NLS-1$ } String realClassName = fullClassName.replace('.', '/') + ".class"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ URL url = originalClassLoader.getResource(realClassName); debug(debugLog, "Found " + realClassName + " in " + url.toString()); //$NON-NLS-1$ //$NON-NLS-2$ try { URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { JarURLConnection jarConnection = (JarURLConnection) connection; debug(debugLog, "hjplugin-rtc jar from the connection " + jarConnection.getJarFileURL()); //$NON-NLS-1$ return jarConnection.getJarFileURL(); } } catch (IOException e) { debug(debugLog, "Unable to obtain URLConnection ", e); //$NON-NLS-1$ } debug(debugLog, "Unable to find hjplugin-rtc.jar"); //$NON-NLS-1$ return null; } private static URL[] getToolkitJarURLs(File toolkitFile, PrintStream debugLog) throws IOException { File[] files = toolkitFile.listFiles(new FileFilter() { public boolean accept(File file) { return file.getName().toLowerCase().endsWith(".jar") && !file.isDirectory(); //$NON-NLS-1$ } }); if (files == null) { throw new RuntimeException(Messages.RTCFacadeFactory_scan_error(toolkitFile.getAbsolutePath())); } // Log what we found if (LOGGER.isLoggable(Level.FINER) || debugLog != null) { String eol = System.getProperty("line.separator"); //$NON-NLS-1$ StringBuilder message = new StringBuilder("Found ").append(files.length) //$NON-NLS-1$ .append(" jars in ").append(toolkitFile.getAbsolutePath()).append(eol); //$NON-NLS-1$ for (File file : files) { message.append(file.getName()).append(eol); } debug(debugLog, message.toString()); } URL[] urls = new URL[files.length]; for (int i = 0; i < files.length; ++i) { urls[i] = files[i].toURI().toURL(); } return urls; } private static void debug(PrintStream debugLog, String msg) { LOGGER.finer(msg); if (debugLog != null) { debugLog.println(msg); } } private static void debug(PrintStream debugLog, String msg, Exception e) { LOGGER.log(Level.FINER, msg, e); if (debugLog != null) { debugLog.println(msg); e.printStackTrace(debugLog); } } }