001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package griffon.util;
019
020 import org.slf4j.Logger;
021 import org.slf4j.LoggerFactory;
022
023 import javax.annotation.Nonnull;
024 import java.io.File;
025 import java.io.IOException;
026 import java.net.JarURLConnection;
027 import java.net.URISyntaxException;
028 import java.net.URL;
029 import java.net.URLConnection;
030 import java.util.Enumeration;
031 import java.util.Scanner;
032 import java.util.jar.JarEntry;
033 import java.util.jar.JarFile;
034
035 import static griffon.core.GriffonExceptionHandler.sanitize;
036 import static griffon.util.GriffonNameUtils.isBlank;
037 import static griffon.util.GriffonNameUtils.requireNonBlank;
038 import static java.util.Objects.requireNonNull;
039
040 /**
041 * @author Andres Almiray
042 * @since 2.0.0
043 */
044 public class ServiceLoaderUtils {
045 private static final Logger LOG = LoggerFactory.getLogger(ServiceLoaderUtils.class);
046 private static final String HASH = "#";
047
048 private ServiceLoaderUtils() {
049 // prevent instantiation
050 }
051
052 public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull Class<?> type, @Nonnull LineProcessor processor) {
053 requireNonNull(classLoader, "Argument 'classLoader' must not be null");
054 requireNonBlank(path, "Argument 'path' must not be blank");
055 requireNonNull(type, "Argument 'type' must not be null");
056 requireNonNull(processor, "Argument 'processor' must not be null");
057 // "The name of a resource is a /-separated path name that identifies the resource."
058 String normalizedPath = path.endsWith("/") ? path : path + "/";
059
060 Enumeration<URL> urls;
061
062 try {
063 urls = classLoader.getResources(normalizedPath + type.getName());
064 } catch (IOException ioe) {
065 LOG.error(ioe.getClass().getName() + " error loading resources of type \"" + type.getName() + "\" from \"" + normalizedPath + "\".");
066 return false;
067 }
068
069 if (urls == null) { return false; }
070
071 while (urls.hasMoreElements()) {
072 URL url = urls.nextElement();
073 LOG.debug("Reading {} definitions from {}", type.getName(), url);
074
075 try (Scanner scanner = new Scanner(url.openStream())) {
076 while (scanner.hasNextLine()) {
077 String line = scanner.nextLine();
078 if (line.startsWith(HASH) || isBlank(line)) { continue; }
079 processor.process(classLoader, type, line);
080 }
081 } catch (IOException e) {
082 LOG.warn("Could not load " + type.getName() + " definitions from " + url, sanitize(e));
083 }
084 }
085
086 return true;
087 }
088
089 public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
090 requireNonNull(classLoader, "Argument 'classLoader' must not be null");
091 requireNonBlank(path, "Argument 'path' must not be blank");
092 requireNonNull(pathFilter, "Argument 'pathFilter' must not be blank");
093 requireNonNull(processor, "Argument 'processor' must not be null");
094
095 Enumeration<URL> urls;
096
097 try {
098 urls = classLoader.getResources(path);
099 } catch (IOException ioe) {
100 LOG.debug(ioe.getClass().getName() + " error loading resources from \"" + path + "\".");
101 return false;
102 }
103
104 if (urls == null) { return false; }
105
106 while (urls.hasMoreElements()) {
107 URL url = urls.nextElement();
108 LOG.debug("Reading definitions from " + url);
109 switch (url.getProtocol()) {
110 case "file":
111 handleFileResource(url, classLoader, path, pathFilter, processor);
112 break;
113 case "jar":
114 handleJarResource(url, classLoader, path, pathFilter, processor);
115 break;
116 default:
117 LOG.warn("Could not load definitions from " + url);
118 }
119 }
120
121 return true;
122 }
123
124 private static void handleFileResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
125 try {
126 File file = new File(url.toURI());
127 for (File entry : file.listFiles()) {
128 if (pathFilter.accept(entry.getName())) {
129 try (Scanner scanner = new Scanner(entry)) {
130 while (scanner.hasNextLine()) {
131 String line = scanner.nextLine();
132 if (line.startsWith(HASH) || isBlank(line)) { continue; }
133 processor.process(classLoader, line);
134 }
135 } catch (IOException e) {
136 LOG.warn("An error occurred while loading resources from " + entry.getAbsolutePath(), sanitize(e));
137 }
138 }
139 }
140 } catch (URISyntaxException e) {
141 LOG.warn("An error occurred while loading resources from " + url, sanitize(e));
142 }
143 }
144
145 private static void handleJarResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
146 try {
147 URLConnection urlConnection = url.openConnection();
148 if (urlConnection instanceof JarURLConnection) {
149 JarURLConnection jarURLConnection = (JarURLConnection) urlConnection;
150 JarFile jar = jarURLConnection.getJarFile();
151 Enumeration<JarEntry> entries = jar.entries();
152 while (entries.hasMoreElements()) {
153 JarEntry jarEntry = entries.nextElement();
154 if (jarEntry.getName().startsWith(path) && pathFilter.accept(jarEntry.getName())) {
155 try (Scanner scanner = new Scanner(jar.getInputStream(jarEntry))) {
156 while (scanner.hasNextLine()) {
157 String line = scanner.nextLine();
158 if (line.startsWith(HASH) || isBlank(line)) { continue; }
159 processor.process(classLoader, line);
160 }
161 } catch (IOException e) {
162 LOG.warn("An error occurred while loading resources from " + jarEntry.getName(), sanitize(e));
163 }
164 }
165 }
166 }
167 } catch (IOException e) {
168 LOG.warn("An error occurred while loading resources from " + url, sanitize(e));
169 }
170 }
171
172 public interface PathFilter {
173 boolean accept(@Nonnull String path);
174 }
175
176 public interface LineProcessor {
177 void process(@Nonnull ClassLoader classLoader, @Nonnull Class<?> type, @Nonnull String line);
178 }
179
180 public interface ResourceProcessor {
181 void process(@Nonnull ClassLoader classLoader, @Nonnull String line);
182 }
183 }
|