/*
* Copyright 2009-2013 Roland Huss
*
* 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 org.jolokia.util;
import java.io.*;
import java.net.URL;
import java.util.*;
/**
* A simple factory for creating services with no-arg constructors from a textual
* descriptor. This descriptor, which must be a resource loadable by this class'
* classloader, is a plain text file which looks like
*
* <pre>
* org.jolokia.detector.TomcatDetector,50
* !org.jolokia.detector.JettyDetector
* org.jolokia.detector.JBossDetector
* org.jolokia.detector.WebsphereDetector,1500
* </pre>
*
* If a line starts with <code>!</code> it is removed if it has been added previously.
* The optional second numeric value is the order in which the services are returned.
*
* @author roland
* @since 05.11.10
*/
public final class ServiceObjectFactory {
private ServiceObjectFactory() {}
/**
* Create a list of services ordered according to the ordering given in the
* service descriptor files. Note, that the descriptor will be looked up
* in the whole classpath space, which can result in reading in multiple
* descriptors with a single path. Note, that the reading order for multiple
* resources with the same name is not defined.
*
* @param pDescriptorPaths a list of resource paths which are handle in the given order.
* Normally, default service should be given as first parameter so that custom
* descriptors have a chance to remove a default service.
* @param <T> type of the service objects to create
* @return a ordered list of created services or an empty list.
*/
public static <T> List<T> createServiceObjects(String... pDescriptorPaths) {
try {
ServiceEntry.initDefaultOrder();
TreeMap<ServiceEntry,T> extractorMap = new TreeMap<ServiceEntry,T>();
for (String descriptor : pDescriptorPaths) {
readServiceDefinitions(extractorMap, descriptor);
}
ArrayList<T> ret = new ArrayList<T>();
for (T service : extractorMap.values()) {
ret.add(service);
}
return ret;
} finally {
ServiceEntry.removeDefaultOrder();
}
}
private static <T> void readServiceDefinitions(Map<ServiceEntry, T> pExtractorMap, String pDefPath) {
try {
for (String url : ClassUtil.getResources(pDefPath)) {
readServiceDefinitionFromUrl(pExtractorMap, url);
}
} catch (IOException e) {
throw new IllegalStateException("Cannot load extractor from " + pDefPath + ": " + e,e);
}
}
private static <T> void readServiceDefinitionFromUrl(Map<ServiceEntry, T> pExtractorMap, String pUrl) {
String line = null;
Exception error = null;
LineNumberReader reader = null;
try {
reader = new LineNumberReader(new InputStreamReader(new URL(pUrl).openStream(),"UTF8"));
line = reader.readLine();
while (line != null) {
createOrRemoveService(pExtractorMap, line);
line = reader.readLine();
}
} catch (ClassNotFoundException e) {
error = e;
} catch (InstantiationException e) {
error = e;
} catch (IllegalAccessException e) {
error = e;
} catch (ClassCastException e) {
error = e;
} catch (IOException e) {
error = e;
} finally {
closeReader(reader);
if (error != null) {
throw new IllegalStateException("Cannot load service " + line + " defined in " +
pUrl + " : " + error + ". Aborting",error);
}
}
}
private static <T> void createOrRemoveService(Map<ServiceEntry, T> pExtractorMap, String pLine)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if (pLine.length() > 0) {
ServiceEntry entry = new ServiceEntry(pLine);
if (entry.isRemove()) {
// Removing is a bit complex since we need to find out
// the proper key since the order is part of equals/hash
// so we cant fetch/remove it directly
Set<ServiceEntry> toRemove = new HashSet<ServiceEntry>();
for (ServiceEntry key : pExtractorMap.keySet()) {
if (key.getClassName().equals(entry.getClassName())) {
toRemove.add(key);
}
}
for (ServiceEntry key : toRemove) {
pExtractorMap.remove(key);
}
} else {
Class<T> clazz = ClassUtil.classForName(entry.getClassName(),ServiceObjectFactory.class.getClassLoader());
if (clazz == null) {
throw new ClassNotFoundException("Class " + entry.getClassName() + " could not be found");
}
T ext = (T) clazz.newInstance();
pExtractorMap.put(entry, ext);
}
}
}
private static void closeReader(LineNumberReader pReader) {
if (pReader != null) {
try {
pReader.close();
} catch (IOException e) {
// Best effort
}
}
}
// =============================================================================
static class ServiceEntry implements Comparable<ServiceEntry> {
private String className;
private boolean remove;
private Integer order;
private static ThreadLocal<Integer> defaultOrderHolder = new ThreadLocal<Integer>() {
/**
* Initialise with start value for entries without an explicite order. 100 in this case.
*
* @return 100
*/
@Override
protected Integer initialValue() {
return Integer.valueOf(100);
}
};
/**
* Parse an entry in the service definition. This should be the full qualified classname
* of a service, optional prefixed with "<code>!</code>" in which case the service is removed
* from the defaul list. An order value can be appened after the classname with a comma for give a
* indication for the ordering of services. If not given, 100 is taken for the first entry, counting up.
*
* @param pLine line to parse
*/
public ServiceEntry(String pLine) {
String[] parts = pLine.split(",");
if (parts[0].startsWith("!")) {
remove = true;
className = parts[0].substring(1);
} else {
remove = false;
className = parts[0];
}
if (parts.length > 1) {
try {
order = Integer.parseInt(parts[1]);
} catch (NumberFormatException exp) {
order = nextDefaultOrder();
}
} else {
order = nextDefaultOrder();
}
}
private Integer nextDefaultOrder() {
Integer defaultOrder = defaultOrderHolder.get();
defaultOrderHolder.set(defaultOrder + 1);
return defaultOrder;
}
private static void initDefaultOrder() {
defaultOrderHolder.set(100);
}
private static void removeDefaultOrder() {
defaultOrderHolder.remove();
}
private String getClassName() {
return className;
}
private boolean isRemove() {
return remove;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
ServiceEntry that = (ServiceEntry) o;
if (!className.equals(that.className)) { return false; }
return true;
}
@Override
public int hashCode() {
return className.hashCode();
}
/** {@inheritDoc} */
public int compareTo(ServiceEntry o) {
return order - o.order;
}
}
}