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