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

org.bimserver.plugins.classloaders.FileJarClassLoader Maven / Gradle / Ivy

Go to download

Shared libraries, common parent for both BIMserver and BimServerClientLib

There is a newer version: 2.0.0
Show newest version
package org.bimserver.plugins.classloaders;

/******************************************************************************
 * Copyright (C) 2009-2018  BIMserver.org
 * 
 * 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 {@literal}.
 *****************************************************************************/

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.bimserver.plugins.PluginManager;
import org.bimserver.utils.PathUtils;
import org.bimserver.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileJarClassLoader extends JarClassLoader implements Closeable {
	private static final Logger LOGGER = LoggerFactory.getLogger(FileJarClassLoader.class);
	private final Map> loadedClasses = new HashMap>();
	private final Map jarContent = new HashMap<>();
	private final Path jarFile;
	private FileSystem fileSystem;
	private boolean embeddedJarFilesLoaded = false;

	public FileJarClassLoader(PluginManager pluginManager, ClassLoader parentClassLoader, Path jarFile) throws FileNotFoundException, IOException {
		super(parentClassLoader);
		this.jarFile = jarFile;
		URI uri = jarFile.toUri();
		try {
			URI x = new URI("jar:" + uri.toString());
			fileSystem = pluginManager.getOrCreateFileSystem(x);
		} catch (URISyntaxException e) {
			LOGGER.error("", e);
		}
	}

	private void loadEmbeddedJarFileSystems() {
		if (!embeddedJarFilesLoaded) {
			loadEmbeddedJarFileSystems(fileSystem.getPath("/"));
			embeddedJarFilesLoaded = true;
		}
	}
	
	private void loadEmbeddedJarFileSystems(Path path) {
		try {
			if (Files.isDirectory(path)) {
				for (Path subPath : PathUtils.list(path)) {
					loadEmbeddedJarFileSystems(subPath);
				}
			} else {
				// This is annoying, but we are caching the contents of JAR files within JAR files in memory, could not get the JarFileSystem to work with jar:jar:file URI's
				// Also there is a problem with not being able to change position within a file, at least in the JarFileSystem
				// It looks like there are 2 other solutions to this problem:
				// - Copy the embedded JAR files to a tmp directory, and load from there with a JarFileSystem wrapper (at some stage we were doing this for all JAR contents, 
				// resulted in 50.000 files, which was annoying, but a few JAR files probably won't hurt
				// - Don't allow plugins to have embedded JAR's, could force them to extract all dependencies...
				//
				if (path.getFileName().toString().toLowerCase().endsWith(".jar")) {
					JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(path));
					try {
						JarEntry jarEntry = jarInputStream.getNextJarEntry();
						while (jarEntry != null) {
							jarContent.put(jarEntry.getName(), IOUtils.toByteArray(jarInputStream));
							jarEntry = jarInputStream.getNextJarEntry();
						}
					} finally {
						jarInputStream.close();
					}
				}
			}
		} catch (IOException e) {
			LOGGER.error("", e);
		}
	}

	@Override
	public URL findResource(String name) {
		try {
			final Lazy lazyInputStream = findPath(name);
			if (lazyInputStream != null) {
				try {
					URL baseUrl = new URL("file:" + name);
					URL url = new URL(baseUrl, name, new URLStreamHandler() {
						@Override
						protected URLConnection openConnection(URL u) throws IOException {
							return new URLConnection(u) {
								@Override
								public void connect() throws IOException {
								}

								@Override
								public InputStream getInputStream() throws IOException {
									return lazyInputStream.get();
								}
							};
						}
					});
					return url;
				} catch (MalformedURLException e) {
					LOGGER.error("", e);
				}
			} else {
				LOGGER.debug("File not found: " + name + " (in " + jarFile.getFileName().toString() + ")");
			}
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	private Lazy findPath(final String name) throws IOException {
		loadEmbeddedJarFileSystems();
		final Path file = this.fileSystem.getPath(name);
		if (Files.exists(file)) {
			return new Lazy(){
				@Override
				public InputStream get() {
					try {
						return Files.newInputStream(file);
					} catch (IOException e) {
						e.printStackTrace();
					}
					return null;
				}};
		}
		if (jarContent.containsKey(name)) {
			return new Lazy(){
				@Override
				public InputStream get() {
					return new ByteArrayInputStream(jarContent.get(name));
				}};
		}
		return null;
	}
	
	@Override
	public Class findClass(String name) throws ClassNotFoundException {
		String fileName = name.replace(".", "/") + ".class";
		if (loadedClasses.containsKey(fileName)) {
			return loadedClasses.get(fileName);
		}
		try {
			Lazy lazyInputStream = findPath(fileName);
			if (lazyInputStream == null) {
				throw new ClassNotFoundException(name);
			}
			InputStream inputStream = lazyInputStream.get();
			ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			try {
				IOUtils.copy(inputStream, byteArrayOutputStream);
			} finally {
				inputStream.close();
			}
			Class defineClass = defineClass(name, byteArrayOutputStream.toByteArray(), 0, byteArrayOutputStream.toByteArray().length);
			loadedClasses.put(fileName, defineClass);
			/*
			 * This is a fix to actually load the package-info.class file with
			 * the annotations about for example namespaces required for JAXB to
			 * work. Found this code here:
			 * https://issues.jboss.org/browse/JBPM-1404
			 */
			if (defineClass != null) {
				final int packageIndex = name.lastIndexOf('.');
				if (packageIndex != -1) {
					final String packageName = name.substring(0, packageIndex);
					final Package classPackage = getPackage(packageName);
					if (classPackage == null) {
						definePackage(packageName, null, null, null, null, null, null, null);
					}
				}
			}				
			return defineClass;
		} catch (FileNotFoundException e) {
			throw new ClassNotFoundException(name);
		} catch (IOException e) {
			throw new ClassNotFoundException(name);
		}
	}

	@Override
	public void dumpStructure(int indent) {
		System.out.print(StringUtils.gen("  ", indent));
		System.out.println("FileJarClassLoader " + jarFile.getFileName().toString());
	}
	
	public void close() throws IOException {
		fileSystem.close();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy