ServiceLoaderUtils.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
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  */
016 package griffon.util;
017 
018 import org.slf4j.Logger;
019 import org.slf4j.LoggerFactory;
020 
021 import javax.annotation.Nonnull;
022 import java.io.File;
023 import java.io.IOException;
024 import java.net.JarURLConnection;
025 import java.net.URISyntaxException;
026 import java.net.URL;
027 import java.net.URLConnection;
028 import java.util.Enumeration;
029 import java.util.Scanner;
030 import java.util.jar.JarEntry;
031 import java.util.jar.JarFile;
032 
033 import static griffon.core.GriffonExceptionHandler.sanitize;
034 import static griffon.util.GriffonNameUtils.isBlank;
035 import static griffon.util.GriffonNameUtils.requireNonBlank;
036 import static java.util.Objects.requireNonNull;
037 
038 /**
039  @author Andres Almiray
040  @since 2.0.0
041  */
042 public class ServiceLoaderUtils {
043     private static final Logger LOG = LoggerFactory.getLogger(ServiceLoaderUtils.class);
044     private static final String JAR_FILE_SCHEME = "jar:file:";
045 
046     private ServiceLoaderUtils() {
047 
048     }
049 
050     public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull Class<?> type, @Nonnull LineProcessor processor) {
051         requireNonNull(classLoader, "Argument 'classLoader' must not be null");
052         requireNonBlank(path, "Argument 'path' must not be blank");
053         requireNonNull(type, "Argument 'type' must not be null");
054         requireNonNull(processor, "Argument 'processor' must not be null");
055         // "The name of a resource is a /-separated path name that identifies the resource."
056         String normalizedPath = path.endsWith("/"? path : path + "/";
057 
058         Enumeration<URL> urls;
059 
060         try {
061             urls = classLoader.getResources(normalizedPath + type.getName());
062         catch (IOException ioe) {
063             LOG.error(ioe.getClass().getName() " error loading resources of type \"" + type.getName() "\" from \"" + normalizedPath + "\".");
064             return false;
065         }
066 
067         if (urls == nullreturn false;
068 
069         while (urls.hasMoreElements()) {
070             URL url = urls.nextElement();
071             LOG.debug("Reading {} definitions from {}", type.getName(), url);
072 
073             try (Scanner scanner = new Scanner(url.openStream())) {
074                 while (scanner.hasNextLine()) {
075                     String line = scanner.nextLine();
076                     if (line.startsWith("#"|| isBlank(line)) continue;
077                     processor.process(classLoader, type, line);
078                 }
079             catch (IOException e) {
080                 LOG.warn("Could not load " + type.getName() " definitions from " + url, sanitize(e));
081             }
082         }
083 
084         return true;
085     }
086 
087     public static boolean load(@Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
088         requireNonNull(classLoader, "Argument 'classLoader' must not be null");
089         requireNonBlank(path, "Argument 'path' must not be blank");
090         requireNonNull(pathFilter, "Argument 'pathFilter' must not be blank");
091         requireNonNull(processor, "Argument 'processor' must not be null");
092 
093         Enumeration<URL> urls;
094 
095         try {
096             urls = classLoader.getResources(path);
097         catch (IOException ioe) {
098             LOG.debug(ioe.getClass().getName() " error loading resources from \"" + path + "\".");
099             return false;
100         }
101 
102         if (urls == nullreturn false;
103 
104         while (urls.hasMoreElements()) {
105             URL url = urls.nextElement();
106             LOG.debug("Reading definitions from " + url);
107             switch (url.getProtocol()) {
108                 case "file":
109                     handleFileResource(url, classLoader, path, pathFilter, processor);
110                     break;
111                 case "jar":
112                     handleJarResource(url, classLoader, path, pathFilter, processor);
113                     break;
114                 default:
115                     LOG.warn("Could not load definitions from " + url);
116             }
117         }
118 
119         return true;
120     }
121 
122     private static void handleFileResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
123         try {
124             File file = new File(url.toURI());
125             for (File entry : file.listFiles()) {
126                 if (pathFilter.accept(entry.getName())) {
127                     try (Scanner scanner = new Scanner(entry)) {
128                         while (scanner.hasNextLine()) {
129                             String line = scanner.nextLine();
130                             if (line.startsWith("#"|| isBlank(line)) continue;
131                             processor.process(classLoader, line);
132                         }
133                     catch (IOException e) {
134                         LOG.warn("An error occurred while loading resources from " + entry.getAbsolutePath(), sanitize(e));
135                     }
136                 }
137             }
138         catch (URISyntaxException e) {
139             LOG.warn("An error occurred while loading resources from " + url, sanitize(e));
140         }
141     }
142 
143     private static void handleJarResource(@Nonnull URL url, @Nonnull ClassLoader classLoader, @Nonnull String path, @Nonnull PathFilter pathFilter, @Nonnull ResourceProcessor processor) {
144         try {
145             URLConnection urlConnection = url.openConnection();
146             if(urlConnection instanceof JarURLConnection) {
147                 JarURLConnection jarURLConnection = (JarURLConnectionurlConnection;
148                 JarFile jar = jarURLConnection.getJarFile();
149                 Enumeration<JarEntry> entries = jar.entries();
150                 while (entries.hasMoreElements()) {
151                     JarEntry jarEntry = entries.nextElement();
152                     if (jarEntry.getName().startsWith(path&& pathFilter.accept(jarEntry.getName())) {
153                         try (Scanner scanner = new Scanner(jar.getInputStream(jarEntry))) {
154                             while (scanner.hasNextLine()) {
155                                 String line = scanner.nextLine();
156                                 if (line.startsWith("#"|| isBlank(line)) continue;
157                                 processor.process(classLoader, line);
158                             }
159                         catch (IOException e) {
160                             LOG.warn("An error occurred while loading resources from " + jarEntry.getName(), sanitize(e));
161                         }
162                     }
163                 }
164             }
165         catch (IOException e) {
166             LOG.warn("An error occurred while loading resources from " + url, sanitize(e));
167         }
168     }
169 
170     public interface PathFilter {
171         boolean accept(@Nonnull String path);
172     }
173 
174     public interface LineProcessor {
175         void process(@Nonnull ClassLoader classLoader, @Nonnull Class<?> type, @Nonnull String line);
176     }
177 
178     public interface ResourceProcessor {
179         void process(@Nonnull ClassLoader classLoader, @Nonnull String line);
180     }
181 }