ServiceLoaderUtils.java
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 = (JarURLConnectionurlConnection;
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 }