/* * Copyright 2010 * Ubiquitous Knowledge Processing (UKP) Lab * Technische Universität Darmstadt * * 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 de.tudarmstadt.ukp.dkpro.core.api.resources; import static org.apache.commons.io.IOUtils.closeQuietly; import static org.apache.commons.io.IOUtils.copy; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.uima.UimaContext; import org.apache.uima.resource.ResourceAccessException; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; /** * @since 1.1.0 */ public class ResourceUtils { private static Map<String, File> urlFileCache; private static Map<String, File> classpathFolderCache; private static final String XDG_RUNTIME_DIR_ENV_VAR = "XDG_RUNTIME_DIR"; private static final String DKPRO_HOME_ENV_VAR = "DKPRO_HOME"; static { urlFileCache = new HashMap<String, File>(); classpathFolderCache = new HashMap<String, File>(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (classpathFolderCache != null) { synchronized (classpathFolderCache) { for (Entry<String, File> e : classpathFolderCache.entrySet()) { if (e.getValue().isDirectory()) { FileUtils.deleteQuietly(e.getValue()); } } } } } }); } /** * Make a given classpath location available as a folder. A temporary folder is created and * deleted upon a regular shutdown of the JVM. This method must not be used for creating * executable binaries. For this purpose, getUrlAsExecutable should be used. * * @param aClasspathBase * a classpath location as used by * {@link PathMatchingResourcePatternResolver#getResources(String)} * @param aCache * use the cache or not. * @return the temporary location on the file system. * @throws IOException if an I/O error has occurred, * @see PathMatchingResourcePatternResolver */ public static File getClasspathAsFolder(String aClasspathBase, boolean aCache) throws IOException { synchronized (classpathFolderCache) { File folder = classpathFolderCache.get(aClasspathBase); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); if (!aCache || (folder == null) || !folder.exists()) { folder = File.createTempFile("dkpro-package", ""); folder.delete(); FileUtils.forceMkdir(folder); Resource[] roots = resolver.getResources(aClasspathBase); for (Resource root : roots) { String base = root.getURL().toString(); Resource[] resources = resolver.getResources(base + "/**/*"); for (Resource resource : resources) { if (!resource.isReadable()) { // This is true for folders/packages continue; } // Relativize String res = resource.getURL().toString(); if (!res.startsWith(base)) { throw new IOException( "Resource location does not start with base location"); } String relative = resource.getURL().toString().substring(base.length()); // Make sure the target folder exists File target = new File(folder, relative).getAbsoluteFile(); if (target.getParentFile() != null) { FileUtils.forceMkdir(target.getParentFile()); } // Copy data InputStream is = null; OutputStream os = null; try { is = resource.getInputStream(); os = new FileOutputStream(target); IOUtils.copyLarge(is, os); } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); } // WORKAROUND: folders get written as files if inside jars // delete files of size zero if (target.length() == 0) { FileUtils.deleteQuietly(target); } } } if (aCache) { classpathFolderCache.put(aClasspathBase, folder); } } return folder; } } /** * Make the given URL available as a file. A temporary file is created and deleted upon a * regular shutdown of the JVM. If the parameter {@code aCache} is {@code true}, the temporary * file is remembered in a cache and if a file is requested for the same URL at a later time, * the same file is returned again. If the previously created file has been deleted meanwhile, * it is recreated from the URL. This method should not be used for creating executable * binaries. For this purpose, getUrlAsExecutable should be used. * * @param aUrl * the URL. * @param aCache * use the cache or not. * @return a file created from the given URL. * @throws IOException * if the URL cannot be accessed to (re)create the file. */ public static File getUrlAsFile(URL aUrl, boolean aCache) throws IOException { return getUrlAsFile(aUrl, aCache, false); } /** * Make the given URL available as a file. A temporary file is created and deleted upon a * regular shutdown of the JVM. If the parameter {@code aCache} is {@code true}, the temporary * file is remembered in a cache and if a file is requested for the same URL at a later time, * the same file is returned again. If the previously created file has been deleted meanwhile, * it is recreated from the URL. This method should not be used for creating executable * binaries. For this purpose, getUrlAsExecutable should be used. * * @param aUrl * the URL. * @param aCache * use the cache or not. * @param aForceTemp * always create a temporary file, even if the URL is already a file. * @return a file created from the given URL. * @throws IOException * if the URL cannot be accessed to (re)create the file. */ public static synchronized File getUrlAsFile(URL aUrl, boolean aCache, boolean aForceTemp) throws IOException { // If the URL already points to a file, there is not really much to do. if (!aForceTemp && "file".equalsIgnoreCase(aUrl.getProtocol())) { try { return new File(aUrl.toURI()); } catch (URISyntaxException e) { throw new IOException(e); } } synchronized (urlFileCache) { // Lets see if we already have a file for this URL in our cache. Maybe // the file has been deleted meanwhile, so we also check if the file // actually still exists on disk. File file = urlFileCache.get(aUrl.toString()); if (!aCache || (file == null) || !file.exists()) { // Create a temporary file and try to preserve the file extension String suffix = FilenameUtils.getExtension(aUrl.getPath()); if (suffix.length() == 0) { suffix = "temp"; } String name = FilenameUtils.getBaseName(aUrl.getPath()); // Get a temporary file which will be deleted when the JVM shuts // down. file = File.createTempFile(name, "." + suffix); file.deleteOnExit(); // Now copy the file from the URL to the file. InputStream is = null; OutputStream os = null; try { is = aUrl.openStream(); os = new FileOutputStream(file); copy(is, os); } finally { closeQuietly(is); closeQuietly(os); } // Remember the file if (aCache) { urlFileCache.put(aUrl.toString(), file); } } return file; } } /** * Make the given URL available as an executable file. A temporary file is created and deleted * upon a regular shutdown of the JVM. If the parameter {@code aCache} is {@code true}, the * temporary file is remembered in a cache and if a file is requested for the same URL at a * later time, the same file is returned again. If the previously created file has been deleted * meanwhile, it is recreated from the URL. * * @param aUrl * the URL. * @param aCache * use the cache or not. * @return an executable file created from the given URL. * @throws IOException * if the file has permissions issues. */ public static synchronized File getUrlAsExecutable(URL aUrl, boolean aCache) throws IOException { File file; synchronized (urlFileCache) { file = urlFileCache.get(aUrl.toString()); if (!aCache || (file == null) || !file.exists()) { String name = FilenameUtils.getBaseName(aUrl.getPath()); file = File.createTempFile(name, ".temp"); file.setExecutable(true); if (!file.canExecute()) { StringBuilder errorMessage = new StringBuilder(128); errorMessage.append("Tried to use temporary folder, but seems it is not " + "executable. Please check the permissions rights from your " + "temporary folder.\n"); if (isEnvironmentVariableDefined(XDG_RUNTIME_DIR_ENV_VAR, errorMessage) && checkFolderPermissions(errorMessage, System.getenv(XDG_RUNTIME_DIR_ENV_VAR))) { file = getFileAsExecutable(aUrl, System.getenv(XDG_RUNTIME_DIR_ENV_VAR)); } else if (isEnvironmentVariableDefined(DKPRO_HOME_ENV_VAR, errorMessage) && checkFolderPermissions(errorMessage, System.getenv(DKPRO_HOME_ENV_VAR) + File.separator + "temp")) { file = getFileAsExecutable(aUrl, System.getenv(DKPRO_HOME_ENV_VAR) + File.separator + "temp"); } else { if (!isUserHomeDefined(errorMessage) || !checkFolderPermissions(errorMessage, System.getProperty("user.home") + File.separator + ".dkpro" + File.separator + "temp")) { throw new IOException(errorMessage.toString()); } file = getFileAsExecutable(aUrl, System.getProperty("user.home") + File.separator + ".dkpro" + File.separator + "temp"); } } file.deleteOnExit(); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = aUrl.openStream(); outputStream = new FileOutputStream(file); copy(inputStream, outputStream); } finally { closeQuietly(inputStream); closeQuietly(outputStream); } if (aCache) { urlFileCache.put(aUrl.toString(), file); } } } return file; } /** * @param is * An {@link InputStream}. * @param filename * The filename this stream was created from. * @return A resolved {@link InputStream} * @throws IOException * if something went wrong during resolving the input stream * @deprecated Use {@link CompressionUtils#getInputStream(String, InputStream)} */ @Deprecated public static InputStream resolveCompressedInputStream(InputStream is, String filename) throws IOException { return CompressionUtils.getInputStream(filename, is); } /** * Resolve a location (which can be many things) to an URL. If the location starts with * {@code classpath:} the location is interpreted as a classpath location. Otherwise it is tried * as a URL, file and at last UIMA resource. If the location is treated as a classpath or file * location, an URL is only returned if the target exists. If it is an URL, it is possible that * the target may not actually exist. * * @param aLocation * a location (classpath, URL, file or UIMA resource location). * @return the resolved URL. * @throws IOException * if the target could not be found. */ public static URL resolveLocation(String aLocation) throws IOException { return resolveLocation(aLocation, null, null); } /** * Resolve a location (which can be many things) to an URL. If the location starts with * {@code classpath:} the location is interpreted as a classpath location. Otherwise it is tried * as a URL, file and at last UIMA resource. If the location is treated as a classpath or file * location, an URL is only returned if the target exists. If it is an URL, it is possible that * the target may not actually exist. * * @param aLocation * a location (classpath, URL, file or UIMA resource location). * @param aContext * a UIMA context. * @return the resolved URL. * @throws IOException * if the target could not be found. */ public static URL resolveLocation(String aLocation, UimaContext aContext) throws IOException { return resolveLocation(aLocation, null, aContext); } /** * Resolve a location (which can be many things) to an URL. If the location starts with * {@code classpath:} the location is interpreted as a classpath location. Otherwise it is tried * as a URL, file and at last UIMA resource. If the location is treated as a classpath or file * location, an URL is only returned if the target exists. If it is an URL, it is possible that * the target may not actually exist. * * @param aLocation * a location (classpath, URL, file or UIMA resource location). * @param aCaller * the instance calling this method (for classpath loading). * @param aContext * a UIMA context. * @return the resolved URL. * @throws IOException * if the target could not be found. */ public static URL resolveLocation(String aLocation, Object aCaller, UimaContext aContext) throws IOException { ClassLoader cl = null; if (aCaller != null) { cl = aCaller.getClass().getClassLoader(); } return resolveLocation(aLocation, cl, aContext); } /** * Resolve a location (which can be many things) to an URL. If the location starts with * {@code classpath:} the location is interpreted as a classpath location. Otherwise it is tried * as a URL, file and at last UIMA resource. If the location is treated as a classpath or file * location, an URL is only returned if the target exists. If it is an URL, it is possible that * the target may not actually exist. * * @param aLocation * a location (classpath, URL, file or UIMA resource location). * @param aClassLoader * the class loader to be used for classpath URLs. * @param aContext * a UIMA context. * @return the resolved URL. * @throws IOException * if the target could not be found. */ public static URL resolveLocation(String aLocation, ClassLoader aClassLoader, UimaContext aContext) throws IOException { // if we have a caller, we use it's classloader ClassLoader classLoader = aClassLoader; if (classLoader == null) { classLoader = ResourceUtils.class.getClassLoader(); } // If a location starts with "classpath:" String prefixClasspath = "classpath:"; if (aLocation.startsWith(prefixClasspath)) { String cpLocation = aLocation.substring(prefixClasspath.length()); if (cpLocation.startsWith("/")) { cpLocation = cpLocation.substring(1); } URL url = classLoader.getResource(cpLocation); if (url == null) { throw new FileNotFoundException("No file found at [" + aLocation + "]"); } return url; } // If it is a true well-formed URL, we assume that it is just that. try { return new URL(aLocation); } catch (MalformedURLException e) { // Ok - was not an URL. } // Otherwise we try if it is a file. File file = new File(aLocation); if (file.exists()) { return file.toURI().toURL(); } // Otherwise we look into the context (if there was one) if (aContext != null) { Exception ex = null; URL url = null; try { url = aContext.getResourceURL(aLocation); } catch (ResourceAccessException e) { ex = e; } if (url == null) { FileNotFoundException e = new FileNotFoundException("No file found at [" + aLocation + "]"); if (ex != null) { e.initCause(ex); } throw e; } return url; } // Otherwise bail out throw new FileNotFoundException("No file found at [" + aLocation + "]"); } /** * * Checks if user.home property is defined in the System. * * @param aStringBuilder * StringBuilder containing an error message for the exception which will be thrown * @return true if the variable is defined * * */ private static boolean isUserHomeDefined(StringBuilder aStringBuilder) { boolean isDefined = System.getProperty("user.home") != null; if (!isDefined) { aStringBuilder.append("user.home folder is not defined."); } return isDefined; } /** * * Checks if an environment variable is defined in the System. * * @param aVariable * Variable's name to be checked in the system. * @param aStringBuilder * StringBuilder containing an error message if an exception is thrown * @return true if the variable is defined * * */ private static boolean isEnvironmentVariableDefined(String aVariable, StringBuilder aStringBuilder) { boolean isDefined = System.getenv(aVariable) != null; if (!isDefined) { aStringBuilder.append("The environment variable: " + aVariable + " is not defined. Please specify this environment variable.\n"); } return isDefined; } /** * * Checks if a directory already exists. If it does not exist it is created. If it already * exists then, its permissions are ok. * * @param aStringBuilder * StringBuilder containing an error message if an exception is thrown * @param aDirectory * String containing the directory path. * @return true if the variable is defined * * */ private static synchronized boolean checkFolderPermissions(StringBuilder aStringBuilder, String aDirectory) { File directory = new File(aDirectory); if (!directory.exists()) { directory.mkdirs(); } if (!directory.canRead()) { aStringBuilder.append("The directory [" + directory + "] is not readable. " + "Please check your permissions rights.\n"); return false; } if (!directory.canWrite()) { aStringBuilder.append("The directory [" + directory + "] is not writable. " + "Please check your permissions rights.\n"); return false; } return true; } /** * * Creates a temporary file in the specified directory for the given URL. * * @param aUrl * URL containing the file's name. * @param aDirectory * String containing the path where the temporary file will be created * @return The temporary executable file * * @throws IOException * If a file could not be created * * */ private static synchronized File getFileAsExecutable(URL aUrl, String aDirectory) throws IOException { return File.createTempFile(FilenameUtils.getBaseName(aUrl.getPath()), ".temp", new File( aDirectory)); } }