/* * Copyright 2008-2017 the original author or authors. * * 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 griffon.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.Enumeration; import java.util.Scanner; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static griffon.core.GriffonExceptionHandler.sanitize; import static griffon.util.GriffonNameUtils.isBlank; import static griffon.util.GriffonNameUtils.requireNonBlank; import static java.util.Objects.requireNonNull; /** * @author Andres Almiray * @since 2.0.0 */ public class ServiceLoaderUtils { private static final Logger LOG = LoggerFactory.getLogger(ServiceLoaderUtils.class); private static final String JAR_FILE_SCHEME = "jar:file:"; private ServiceLoaderUtils() { } public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull Class<?> type, @Nonnull LineProcessor processor) { requireNonNull(classLoader, "Argument 'classLoader' must not be null"); requireNonBlank(path, "Argument 'path' must not be blank"); requireNonNull(type, "Argument 'type' must not be null"); requireNonNull(processor, "Argument 'processor' must not be null"); // "The name of a resource is a /-separated path name that identifies the resource." String normalizedPath = path.endsWith("/") ? path : path + "/"; Enumeration<URL> urls; try { urls = classLoader.getResources(normalizedPath + type.getName()); } catch (IOException ioe) { LOG.error(ioe.getClass().getName() + " error loading resources of type \"" + type.getName() + "\" from \"" + normalizedPath + "\"."); return false; } if (urls == null) return false; while (urls.hasMoreElements()) { URL url = urls.nextElement(); LOG.debug("Reading {} definitions from {}", type.getName(), url); try (Scanner scanner = new Scanner(url.openStream())) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("#") || isBlank(line)) continue; processor.process(classLoader, type, line); } } catch (IOException e) { LOG.warn("Could not load " + type.getName() + " definitions from " + url, sanitize(e)); } } return true; } public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) { requireNonNull(classLoader, "Argument 'classLoader' must not be null"); requireNonBlank(path, "Argument 'path' must not be blank"); requireNonNull(pathFilter, "Argument 'pathFilter' must not be blank"); requireNonNull(processor, "Argument 'processor' must not be null"); Enumeration<URL> urls; try { urls = classLoader.getResources(path); } catch (IOException ioe) { LOG.debug(ioe.getClass().getName() + " error loading resources from \"" + path + "\"."); return false; } if (urls == null) return false; while (urls.hasMoreElements()) { URL url = urls.nextElement(); LOG.debug("Reading definitions from " + url); switch (url.getProtocol()) { case "file": handleFileResource(url, classLoader, path, pathFilter, processor); break; case "jar": handleJarResource(url, classLoader, path, pathFilter, processor); break; default: LOG.warn("Could not load definitions from " + url); } } return true; } private static void handleFileResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) { try { File file = new File(url.toURI()); for (File entry : file.listFiles()) { if (pathFilter.accept(entry.getName())) { try (Scanner scanner = new Scanner(entry)) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("#") || isBlank(line)) continue; processor.process(classLoader, line); } } catch (IOException e) { LOG.warn("An error occurred while loading resources from " + entry.getAbsolutePath(), sanitize(e)); } } } } catch (URISyntaxException e) { LOG.warn("An error occurred while loading resources from " + url, sanitize(e)); } } private static void handleJarResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) { try { URLConnection urlConnection = url.openConnection(); if(urlConnection instanceof JarURLConnection) { JarURLConnection jarURLConnection = (JarURLConnection) urlConnection; JarFile jar = jarURLConnection.getJarFile(); Enumeration<JarEntry> entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); if (jarEntry.getName().startsWith(path) && pathFilter.accept(jarEntry.getName())) { try (Scanner scanner = new Scanner(jar.getInputStream(jarEntry))) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("#") || isBlank(line)) continue; processor.process(classLoader, line); } } catch (IOException e) { LOG.warn("An error occurred while loading resources from " + jarEntry.getName(), sanitize(e)); } } } } } catch (IOException e) { LOG.warn("An error occurred while loading resources from " + url, sanitize(e)); } } public interface PathFilter { boolean accept(@Nonnull String path); } public interface LineProcessor { void process(@Nonnull ClassLoader classLoader, @Nonnull Class<?> type, @Nonnull String line); } public interface ResourceProcessor { void process(@Nonnull ClassLoader classLoader, @Nonnull String line); } }