/*
* Copyright 2013 Blue Lotus Software, LLC.
*
* 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.
*/
/*
* $Id$
*/
package com.bluelotussoftware.service;
import com.bluelotussoftware.service.spi.Log;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
/**
* Singleton {@link Log} service with {@link ClassLoader} and
* {@link ServiceLoader} reloading capabilities for use in a web application.
*
* @author John Yeary <jyeary@bluelotussoftware.com>
* @version 1.0
*/
public class LogService {
private static LogService service;
private ServiceLoader serviceLoader;
private LogService() {
serviceLoader = ServiceLoader.load(Log.class);
}
public static synchronized LogService getInstance() {
if (service == null) {
service = new LogService();
}
return service;
}
public List<Log> getLoggers() {
List<Log> loggers = new ArrayList<>();
Iterator<Log> it = serviceLoader.iterator();
while (it.hasNext()) {
loggers.add(it.next());
}
return loggers;
}
public Log getFirstAvailableLogger() {
Log log = null;
Iterator<Log> it = serviceLoader.iterator();
while (it.hasNext()) {
log = it.next();
break;
}
return log;
}
public void reload() throws MalformedURLException, IOException, ClassNotFoundException {
// 1. Read the WEB-INF/lib directory and get a list of jar files.
// 2. Examine the jar files and look for META-INF/services directories.
// 3. If the directory has services, read the file names from each service entry.
// 4. Store the class names in a list.
// 5. Create a list of URLs for files.
// 6. Create a URLClassLoader, add the parent ClassLoader and URLs.
// 7. Call URLClassloader.loadClass(String name) using saved names.
// 8. Create a new ServiceLoader instance with the URLClassLoader, and call load on the interface.
ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
Path webInfLibDirectory = Paths.get(context.getRealPath("WEB-INF/lib"));
URLClassLoader urlcl;
List<URL> jarURLs = new ArrayList<>();
FileSystemProvider provider = getZipFileSystemProvider();
List<String> implementationsToLoad = new ArrayList<>();
if (Files.exists(webInfLibDirectory, LinkOption.NOFOLLOW_LINKS)) {
List<Path> files = listJars(webInfLibDirectory);
for (Path p : files) {
info("LOCATED JAR " + p.toFile().getName());
jarURLs.add(p.toUri().toURL());
FileSystem fs = provider.newFileSystem(p, new HashMap<String, Object>());
Path serviceDirectory = fs.getPath("/META-INF", "services");
info("SCANNING SERVICES");
if (Files.exists(serviceDirectory)) {
DirectoryStream<Path> serviceListings = Files.newDirectoryStream(serviceDirectory);
for (Path px : serviceListings) {
List<String> services = Files.readAllLines(px, Charset.forName("UTF-8"));
info(MessageFormat.format("SERVICES FOUND: {0}", Arrays.toString(services.toArray())));
implementationsToLoad.addAll(services);
}
}
}
urlcl = new URLClassLoader(jarURLs.toArray(new URL[jarURLs.size()]), context.getClassLoader());
load(implementationsToLoad, urlcl);
serviceLoader = ServiceLoader.load(Log.class, urlcl);
Iterator<Log> it = serviceLoader.iterator();
while (it.hasNext()) {
info(it.next().getClass().getName());
}
}
}
private List<Path> listJars(Path path) throws IOException {
List<Path> jars = new ArrayList<>();
DirectoryStream<Path> ds = Files.newDirectoryStream(path, "*.jar");
for (Path child : ds) {
if (!Files.isDirectory(child)) {
jars.add(child);
}
}
return jars;
}
private void load(final List<String> FQCN, final ClassLoader classLoader)
throws ClassNotFoundException {
for (String s : FQCN) {
info(MessageFormat.format("LOAD CLASS {0}", s));
Class<?> clazz = classLoader.loadClass(s);
info(MessageFormat.format("CLASS {0} LOADED", clazz.getName()));
}
}
private static FileSystemProvider getZipFileSystemProvider() {
for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
if ("jar".equals(provider.getScheme())) {
return provider;
}
}
return null;
}
private void info(final String message) {
getFirstAvailableLogger().info(message);
}
}