001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.utils.file; 015 016import org.gbif.utils.PreconditionUtils; 017 018import java.io.File; 019import java.io.IOException; 020import java.net.URISyntaxException; 021import java.net.URL; 022import java.net.URLDecoder; 023import java.nio.file.Files; 024import java.nio.file.Paths; 025import java.util.Enumeration; 026import java.util.HashSet; 027import java.util.Set; 028import java.util.jar.JarEntry; 029import java.util.jar.JarFile; 030 031import org.apache.commons.lang3.ObjectUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * Utils class dealing with classpath resources. 038 */ 039public final class ResourcesUtil { 040 041 private static final Logger LOG = LoggerFactory.getLogger(ResourcesUtil.class); 042 043 /** 044 * Static utils class. 045 */ 046 private ResourcesUtil() {} 047 048 /** 049 * Copies classpath resources to real files. 050 * 051 * @param folder to copy resource files into 052 * @param ignoreMissingResources if true ignores missing resources, throws IOException otherwise 053 * @param classpathPrefix common prefix added to all classpath resources which is not used for the result file 054 * path. 055 * @param classpathResources list of classpath resources to be copied into folder 056 */ 057 public static void copy( 058 File folder, 059 String classpathPrefix, 060 boolean ignoreMissingResources, 061 String... classpathResources) 062 throws IOException { 063 for (String classpathResource : classpathResources) { 064 String res = classpathPrefix + classpathResource; 065 URL url; 066 try { 067 url = getResource(res); 068 if (url == null) { 069 throw new IllegalArgumentException("Classpath resource " + res + " not existing"); 070 } 071 } catch (IllegalArgumentException e) { 072 if (ignoreMissingResources) { 073 LOG.debug("Resource {} not found", res); 074 continue; 075 } 076 throw new IOException(e); 077 } 078 079 File f = new File(folder, classpathResource); 080 FileUtils.createParentDirs(f); 081 082 try { 083 Files.copy(Paths.get(url.toURI()), f.toPath()); 084 } catch (URISyntaxException e) { 085 throw new IOException(e); 086 } 087 } 088 } 089 090 /** 091 * Returns a {@code URL} pointing to {@code resourceName} if the resource is found using the 092 * {@linkplain Thread#getContextClassLoader() context class loader}. In simple environments, the 093 * context class loader will find resources from the class path. In environments where different 094 * threads can have different class loaders, for example app servers, the context class loader 095 * will typically have been set to an appropriate loader for the current thread. 096 * 097 * <p>In the unusual case where the context class loader is null, the class loader that loaded 098 * this class will be used instead. 099 * 100 * <p>From Guava. 101 * 102 * @throws IllegalArgumentException if the resource is not found 103 */ 104 public static URL getResource(String resourceName) { 105 ClassLoader loader = 106 ObjectUtils.firstNonNull( 107 Thread.currentThread().getContextClassLoader(), ResourcesUtil.class.getClassLoader()); 108 URL url = loader.getResource(resourceName); 109 PreconditionUtils.checkArgument(url != null, "resource " + resourceName + " not found."); 110 return url; 111 } 112 113 /** 114 * List directory contents for a resource folder. Not recursive. 115 * Works for regular files and also JARs. 116 * 117 * Based on code from Greg Briggs, slightly modified. 118 * 119 * @param clazz Any java class that lives in the same place as the resources you want. 120 * @param path Should end with "/", but not start with one. 121 * @return Just the name of each member item, not the full paths. Empty array in case folder cannot be found 122 * @throws IOException 123 */ 124 public static String[] list(Class clazz, String path) throws IOException { 125 if (!path.endsWith("/")) { 126 path = path + "/"; 127 } 128 URL dirURL = clazz.getClassLoader().getResource(path); 129 if (dirURL != null && dirURL.getProtocol().equals("file")) { 130 /* A file path: easy enough */ 131 try { 132 return new File(dirURL.toURI()).list(); 133 } catch (URISyntaxException e) { 134 throw new IOException( 135 "Bad URI. Cannot list files for path " + path + " in class " + clazz, e); 136 } 137 } 138 139 if (dirURL == null) { 140 /* 141 * In case of a jar file, we can't actually find a directory. 142 * Have to assume the same jar as clazz. 143 */ 144 String me = clazz.getName().replace(".", "/") + ".class"; 145 dirURL = clazz.getClassLoader().getResource(me); 146 } 147 148 if (dirURL.getProtocol().equals("jar")) { 149 /* A JAR path */ 150 String jarPath = 151 dirURL 152 .getPath() 153 .substring(5, dirURL.getPath().indexOf("!")); // strip out only the JAR file 154 JarFile jar = new JarFile(URLDecoder.decode(jarPath, FileUtils.UTF8)); 155 Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar 156 Set<String> result = new HashSet<>(); // avoid duplicates in case it is a subdirectory 157 while (entries.hasMoreElements()) { 158 String name = entries.nextElement().getName(); 159 if (name.startsWith(path)) { // filter according to the path 160 String entry = name.substring(path.length()); 161 if (!StringUtils.isBlank(entry)) { 162 int checkSubdir = entry.indexOf("/"); 163 if (checkSubdir >= 0) { 164 // if it is a subdirectory, we just return the directory name 165 entry = entry.substring(0, checkSubdir); 166 } 167 result.add(entry); 168 } 169 } 170 } 171 return result.toArray(new String[result.size()]); 172 } 173 174 return new String[] {}; 175 } 176}