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