/*
* Copyright (c) 2008-2013, Hazelcast, Inc. All Rights Reserved.
*
* 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.
*/
package com.hazelcast.util;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.hazelcast.nio.ClassLoaderUtil;
import com.hazelcast.nio.IOUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Support class for loading Hazelcast services and hooks based on the Java ServiceLoader specification
* but changed in the fact of classloaders to test for given services to work in multi classloader
* environments like application or OSGi servers
*/
public final class ServiceLoader {
private static final ILogger LOGGER = Logger.getLogger(ServiceLoader.class);
private static final String FILTERING_CLASS_LOADER = FilteringClassLoader.class.getCanonicalName();
private ServiceLoader() {
}
public static <T> T load(Class<T> clazz, String factoryId, ClassLoader classLoader)
throws Exception {
final Iterator<T> iterator = iterator(clazz, factoryId, classLoader);
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
public static <T> Iterator<T> iterator(final Class<T> clazz, String factoryId, ClassLoader classLoader)
throws Exception {
final List<ClassLoader> classLoaders = selectClassLoaders(classLoader);
final Set<URLDefinition> factoryUrls = new HashSet<URLDefinition>();
for (ClassLoader selectedClassLoader : classLoaders) {
factoryUrls.addAll(collectFactoryUrls(factoryId, selectedClassLoader));
}
final Set<ServiceDefinition> serviceDefinitions = new HashSet<ServiceDefinition>();
for (URLDefinition urlDefinition : factoryUrls) {
serviceDefinitions.addAll(parse(urlDefinition));
}
if (serviceDefinitions.isEmpty()) {
Logger.getLogger(ServiceLoader.class).warning(
"Service loader could not load 'META-INF/services/" + factoryId + "' It may be empty or does not exist.");
}
return new Iterator<T>() {
final Iterator<ServiceDefinition> iterator = serviceDefinitions.iterator();
public boolean hasNext() {
return iterator.hasNext();
}
public T next() {
final ServiceDefinition definition = iterator.next();
try {
String className = definition.className;
ClassLoader classLoader = definition.classLoader;
return clazz.cast(ClassLoaderUtil.newInstance(classLoader, className));
} catch (Exception e) {
throw new HazelcastException(e);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private static Set<URLDefinition> collectFactoryUrls(String factoryId, ClassLoader classLoader) {
final String resourceName = "META-INF/services/" + factoryId;
try {
final Enumeration<URL> configs;
if (classLoader != null) {
configs = classLoader.getResources(resourceName);
} else {
configs = ClassLoader.getSystemResources(resourceName);
}
Set<URLDefinition> urlDefinitions = new HashSet<URLDefinition>();
while (configs.hasMoreElements()) {
URL url = configs.nextElement();
ClassLoader highestClassLoader = findHighestReachableClassLoader(url, classLoader, resourceName);
urlDefinitions.add(new URLDefinition(url, highestClassLoader));
}
return urlDefinitions;
} catch (Exception e) {
LOGGER.severe(e);
}
return Collections.emptySet();
}
private static Set<ServiceDefinition> parse(URLDefinition urlDefinition) {
try {
final Set<ServiceDefinition> names = new HashSet<ServiceDefinition>();
BufferedReader r = null;
try {
URL url = urlDefinition.url;
r = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
while (true) {
String line = r.readLine();
if (line == null) {
break;
}
int comment = line.indexOf('#');
if (comment >= 0) {
line = line.substring(0, comment);
}
String name = line.trim();
if (name.length() == 0) {
continue;
}
names.add(new ServiceDefinition(name, urlDefinition.classLoader));
}
} finally {
IOUtil.closeResource(r);
}
return names;
} catch (Exception e) {
LOGGER.severe(e);
}
return Collections.emptySet();
}
private static ClassLoader findHighestReachableClassLoader(URL url, ClassLoader classLoader, String resourceName) {
if (classLoader.getParent() == null) {
return classLoader;
}
ClassLoader highestClassLoader = classLoader;
ClassLoader current = classLoader;
while (current.getParent() != null) {
// If we have a filtering classloader in hierarchy we need to stop!
if (FILTERING_CLASS_LOADER.equals(current.getClass().getCanonicalName())) {
break;
}
ClassLoader parent = current.getParent();
try {
Enumeration<URL> enumeration = parent.getResources(resourceName);
while (enumeration.hasMoreElements()) {
URL testURL = enumeration.nextElement();
if (url.equals(testURL)) {
highestClassLoader = parent;
}
}
//CHECKSTYLE:OFF
} catch (IOException ignore) {
// We want to ignore failures and keep searching
}
//CHECKSTYLE:ON
// Going on with the search upwards the hierarchy
current = current.getParent();
}
return highestClassLoader;
}
static List<ClassLoader> selectClassLoaders(ClassLoader classLoader) {
// List prevents reordering!
List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
if (classLoader != null) {
classLoaders.add(classLoader);
}
// Is TCCL same as given classLoader
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl != classLoader) {
classLoaders.add(tccl);
}
// Hazelcast core classLoader
ClassLoader coreClassLoader = ServiceLoader.class.getClassLoader();
if (coreClassLoader != classLoader && coreClassLoader != tccl) {
classLoaders.add(coreClassLoader);
}
// Hazelcast client classLoader
try {
Class<?> hzClientClass = Class.forName("com.hazelcast.client.HazelcastClient");
ClassLoader clientClassLoader = hzClientClass.getClassLoader();
if (clientClassLoader != classLoader && clientClassLoader != tccl && clientClassLoader != coreClassLoader) {
classLoaders.add(clientClassLoader);
}
//CHECKSTYLE:OFF
} catch (ClassNotFoundException ignore) {
// ignore since we does not have HazelcastClient in classpath
}
//CHECKSTYLE:ON
return classLoaders;
}
/**
* Definition of the internal service based on classloader that is able to load it
* and the classname of the found service.
*/
private static final class ServiceDefinition {
private final String className;
private final ClassLoader classLoader;
private ServiceDefinition(String className, ClassLoader classLoader) {
ValidationUtil.isNotNull(className, "className");
ValidationUtil.isNotNull(classLoader, "classLoader");
this.className = className;
this.classLoader = classLoader;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ServiceDefinition that = (ServiceDefinition) o;
if (!classLoader.equals(that.classLoader)) {
return false;
}
if (!className.equals(that.className)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = className.hashCode();
result = 31 * result + classLoader.hashCode();
return result;
}
}
/**
* This class keeps track of available service definition URLs and
* the corresponding classloaders
*/
private static final class URLDefinition {
private final URL url;
private final ClassLoader classLoader;
private URLDefinition(URL url, ClassLoader classLoader) {
this.url = url;
this.classLoader = classLoader;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
URLDefinition that = (URLDefinition) o;
if (url != null ? !url.equals(that.url) : that.url != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = url != null ? url.hashCode() : 0;
return result;
}
}
}