001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.utils; 019 020import io.stallion.exceptions.NotFoundException; 021import io.stallion.exceptions.UsageException; 022import io.stallion.plugins.StallionJavaPlugin; 023import io.stallion.plugins.PluginRegistry; 024import io.stallion.services.Log; 025import io.stallion.settings.Settings; 026import org.apache.commons.io.FilenameUtils; 027import org.apache.commons.io.IOUtils; 028import org.apache.commons.lang3.StringUtils; 029import org.parboiled.common.FileUtils; 030 031import java.io.*; 032import java.net.URL; 033import java.net.URLDecoder; 034import java.nio.charset.Charset; 035import java.util.*; 036import java.util.jar.JarEntry; 037import java.util.jar.JarFile; 038 039import static io.stallion.utils.Literals.*; 040 041 042public class ResourceHelpers { 043 044 public static String loadAssetResource(String pluginName, String path) throws IOException { 045 if (path.contains("..") || (!path.startsWith("/assets/") && path.startsWith("assets"))) { 046 throw new UsageException("Invalid path: " + path); 047 } 048 return loadResource(pluginName, path); 049 } 050 051 public static String loadTemplateResource(String pluginName, String path) throws IOException { 052 if (path.contains("..") || (!path.startsWith("/assets/") && path.startsWith("assets"))) { 053 throw new UsageException("Invalid path: " + path); 054 } 055 return loadResource(pluginName, path); 056 } 057 058 public static boolean resourceExists(String pluginName, String path) { 059 URL url = null; 060 try { 061 url = pluginPathToUrl(pluginName, path); 062 } catch (FileNotFoundException e) { 063 return false; 064 } 065 if (url == null) { 066 return false; 067 } else { 068 return true; 069 } 070 } 071 072 public static URL getUrlOrNotFound(String pluginName, String path) { 073 try { 074 URL url = pluginPathToUrl(pluginName, path); 075 if (url == null) { 076 throw new NotFoundException("Resource not found: " + pluginName + ":" + path); 077 } 078 return url; 079 } catch(FileNotFoundException e) { 080 throw new NotFoundException("Resource not found: " + pluginName + ":" + path); 081 } 082 } 083 084 public static String loadResource(String pluginName, String path) { 085 try { 086 URL url = pluginPathToUrl(pluginName, path); 087 if (url == null) { 088 throw new FileNotFoundException("Resource not found: " + path); 089 } 090 return loadResourceFromUrl(url, pluginName); 091 } catch (IOException e) { 092 throw new RuntimeException(e); 093 } 094 } 095 096 public static List<String> listFilesInDirectory(String plugin, String path) { 097 String ending = ""; 098 String starting = ""; 099 if (path.contains("*")) { 100 String[] parts = StringUtils.split(path, "*", 2); 101 String base = parts[0]; 102 if (!base.endsWith("/")) { 103 path = new File(base).getParent(); 104 starting = FilenameUtils.getName(base); 105 } else { 106 path = base; 107 } 108 ending = parts[1]; 109 } 110 111 Log.info("listFilesInDirectory Parsed Path {0} starting:{1} ending:{2}", path, starting, ending); 112 URL url = PluginRegistry.instance().getJavaPluginByName().get(plugin).getClass().getResource(path); 113 Log.info("URL: {0}", url); 114 115 List<String> filenames = new ArrayList<>(); 116 URL dirURL = getClassForPlugin(plugin).getResource(path); 117 Log.info("Dir URL is {0}", dirURL); 118 // Handle file based resource folder 119 if (dirURL != null && dirURL.getProtocol().equals("file")) { 120 String fullPath = dirURL.toString().substring(5); 121 File dir = new File(fullPath); 122 // In devMode, use the source resource folder, rather than the compiled version 123 if (Settings.instance().getDevMode()) { 124 String devPath = fullPath.replace("/target/classes/", "/src/main/resources/"); 125 File devFolder = new File(devPath); 126 if (devFolder.exists()){ 127 dir = devFolder; 128 } 129 } 130 Log.info("List files from folder {0}", dir.getAbsolutePath()); 131 List<String> files = list(); 132 for (String name: dir.list()) { 133 if (!empty(ending) && !name.endsWith(ending)) { 134 continue; 135 } 136 if (!empty(starting) && !name.endsWith("starting")) { 137 continue; 138 } 139 // Skip special files, hidden files 140 if (name.startsWith(".") || name.startsWith("~") || name.startsWith("#") || name.contains("_flymake.")) { 141 continue; 142 } 143 filenames.add(path + name); 144 } 145 return filenames; 146 } 147 148 149 if (dirURL.getProtocol().equals("jar")) { 150 /* A JAR path */ 151 String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file 152 JarFile jar = null; 153 try { 154 jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); 155 } catch (IOException e) { 156 throw new RuntimeException(e); 157 } 158 if (path.startsWith("/")) { 159 path = path.substring(1); 160 } 161 Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar 162 Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory 163 while(entries.hasMoreElements()) { 164 String name = entries.nextElement().getName(); 165 Log.finer("Jar file entry: {0}", name); 166 if (name.startsWith(path)) { //filter according to the path 167 String entry = name.substring(path.length()); 168 int checkSubdir = entry.indexOf("/"); 169 if (checkSubdir >= 0) { 170 // if it is a subdirectory, we just return the directory name 171 entry = entry.substring(0, checkSubdir); 172 } 173 if (!empty(ending) && !name.endsWith(ending)) { 174 continue; 175 } 176 if (!empty(starting) && !name.endsWith("starting")) { 177 continue; 178 } 179 // Skip special files, hidden files 180 if (name.startsWith(".") || name.startsWith("~") || name.startsWith("#") || name.contains("_flymake.")) { 181 continue; 182 } 183 result.add(entry); 184 } 185 } 186 return new ArrayList<>(result); 187 } 188 throw new UnsupportedOperationException("Cannot list files for URL "+dirURL); 189 /* 190 try { 191 URL url1 = getClassForPlugin(plugin).getResource(path); 192 Log.info("URL1 {0}", url1); 193 if (url1 != null) { 194 Log.info("From class folder contents {0}", IOUtils.toString(url1)); 195 Log.info("From class folder contents as stream {0}", IOUtils.toString(getClassForPlugin(plugin).getResourceAsStream(path))); 196 } 197 URL url2 = getClassLoaderForPlugin(plugin).getResource(path); 198 Log.info("URL1 {0}", url2); 199 if (url2 != null) { 200 Log.info("From classLoader folder contents {0}", IOUtils.toString(url2)); 201 Log.info("From classLoader folder contents as stream {0}", IOUtils.toString(getClassLoaderForPlugin(plugin).getResourceAsStream(path))); 202 } 203 204 } catch (IOException e) { 205 Log.exception(e, "error loading path " + path); 206 } 207 // Handle jar based resource folder 208 try( 209 InputStream in = getResourceAsStream(plugin, path); 210 BufferedReader br = new BufferedReader( new InputStreamReader( in ) ) ) { 211 String resource; 212 while( (resource = br.readLine()) != null ) { 213 Log.finer("checking resource for inclusion in directory scan: {0}", resource); 214 if (!empty(ending) && !resource.endsWith(ending)) { 215 continue; 216 } 217 if (!empty(starting) && !resource.endsWith("starting")) { 218 continue; 219 } 220 // Skip special files, hidden files 221 if (resource.startsWith(".") || resource.startsWith("~") || resource.startsWith("#") || resource.contains("_flymake.")) { 222 continue; 223 } 224 Log.finer("added resource during directory scan: {0}", resource); 225 filenames.add(path + resource); 226 } 227 } catch (IOException e) { 228 throw new RuntimeException(e); 229 } 230 return filenames; 231 */ 232 } 233 234 private static InputStream getResourceAsStream(String plugin, String resource ) { 235 236 if (empty(plugin) || plugin.equals("stallion")) { 237 return ResourceHelpers.class.getClassLoader().getResourceAsStream(resource); 238 } else { 239 return PluginRegistry.instance().getJavaPluginByName().get(plugin).getClass().getClassLoader().getResourceAsStream(resource); 240 } 241 } 242 243 private static ClassLoader getClassLoaderForPlugin(String plugin) { 244 if (empty(plugin) || "stallion".equals(plugin)) { 245 return ResourceHelpers.class.getClassLoader(); 246 } else { 247 return PluginRegistry.instance().getJavaPluginByName().get(plugin).getClass().getClassLoader(); 248 } 249 } 250 251 private static Class getClassForPlugin(String plugin) { 252 if (empty(plugin) || "stallion".equals(plugin)) { 253 return ResourceHelpers.class; 254 } else { 255 return PluginRegistry.instance().getJavaPluginByName().get(plugin).getClass(); 256 } 257 } 258 259 260 public static URL pluginPathToUrl(String pluginName, String path) throws FileNotFoundException { 261 URL url = null; 262 if ("stallion".equals(pluginName) || empty(pluginName)) { 263 url = ResourceHelpers.class.getResource(path); 264 } else { 265 StallionJavaPlugin booter = PluginRegistry.instance().getJavaPluginByName().get(pluginName); 266 if (booter == null) { 267 throw new FileNotFoundException("No plugin found: " + pluginName); 268 } 269 url = booter.getClass().getResource(path); 270 } 271 return url; 272 } 273 274 public static byte[] loadBinaryResource(String pluginName, String path) { 275 try { 276 URL url = pluginPathToUrl(pluginName, path); 277 if (url == null) { 278 throw new FileNotFoundException("Resource not found: " + path); 279 } 280 return loadBinaryResource(url, pluginName); 281 } catch (IOException e) { 282 throw new RuntimeException(e); 283 } 284 } 285 286 public static byte[] loadBinaryResource(URL resourceUrl, String pluginName) { 287 288 try { 289 File file = urlToFileMaybe(resourceUrl, pluginName); 290 if (file == null) { 291 return IOUtils.toByteArray(resourceUrl.openStream()); 292 } else { 293 return FileUtils.readAllBytes(file); 294 } 295 } catch (IOException e) { 296 throw new RuntimeException(e); 297 } 298 } 299 300 public static String loadResourceFromUrl(URL resourceUrl) throws IOException { 301 return loadResourceFromUrl(resourceUrl, ""); 302 } 303 304 public static String loadResourceFromUrl(URL resourceUrl, String pluginName) throws IOException { 305 File file = urlToFileMaybe(resourceUrl, pluginName) ; 306 if (file != null) { 307 return FileUtils.readAllText(file, Charset.forName("UTF-8")); 308 } else { 309 return IOUtils.toString(resourceUrl, Charset.forName("UTF-8")); 310 311 } 312 } 313 314 public static File findDevModeFileForResource(String plugin, String resourcePath) { 315 if (!Settings.instance().getDevMode()) { 316 throw new UsageException("You can only call this method in dev mode!"); 317 } 318 try { 319 if (!resourcePath.startsWith("/")) { 320 resourcePath = "/" + resourcePath; 321 } 322 // We find the URL of the root, not the actual file, since if the file was just created, we want it to be 323 // accessible even if mvn hasn't recompiled the project. This allows us to iterate more quickly. 324 URL url = pluginPathToUrl(plugin, resourcePath); 325 // Maybe the file was just created? We'll try to find the root folder path, and then add the file path 326 if (url == null) { 327 url = new URL(StringUtils.stripStart(url.toString(), "/") + resourcePath); 328 } 329 return urlToFileMaybe(url, plugin); 330 } catch (IOException e) { 331 throw new RuntimeException(e); 332 } 333 } 334 335 private static File urlToFileMaybe(URL resourceUrl, String pluginName) { 336 Log.finer("Load resource URL={0} plugin={1}", resourceUrl, pluginName); 337 if (!Settings.instance().getDevMode()) { 338 return null; 339 } 340 341 // If the resourceUrl points to a local file, and the file exists in the file system, then use that file 342 Log.finer("Resource URL {0}", resourceUrl); 343 if (resourceUrl.toString().startsWith("file:/")) { 344 String path = resourceUrl.toString().substring(5).replace("/target/classes/", "/src/main/resources/"); 345 File file = new File(path); 346 if (file.isFile()) { 347 Log.finest("Load resource from source path {0}", path); 348 return file; 349 } 350 } 351 352 String[] parts = resourceUrl.toString().split("!", 2); 353 if (parts.length < 2) { 354 return null; 355 } 356 String relativePath = parts[1]; 357 if (!relativePath.startsWith("/")) { 358 relativePath = "/" + relativePath; 359 } 360 if (relativePath.contains("..")) { 361 throw new UsageException("Invalid characters in the URL path: " + resourceUrl.toString()); 362 } 363 364 List<String> paths = list(); 365 if (empty(pluginName) || "stallion".equals(pluginName)) { 366 paths.add(System.getProperty("user.home") + "/st/core/src/main/resources" + relativePath); 367 paths.add(System.getProperty("user.home") + "/stallion/core/src/main/resources" + relativePath); 368 } else { 369 paths.add(System.getProperty("user.home") + "/st/" + pluginName + "/src/main/resources" + relativePath); 370 paths.add(System.getProperty("user.home") + "/stallion/" + pluginName + "/src/main/resources" + relativePath); 371 } 372 boolean isText = true; 373 374 for (String path: paths) { 375 File file = new File(path); 376 if (file.isFile()) { 377 Log.finest("Load resource from guessed path {0}", path); 378 return file; 379 } 380 } 381 382 // All else fails, just return it based on the resourc 383 Log.finest("Could not find a devMode version for resource path {0}", resourceUrl.toString()); 384 return null; 385 386 } 387}