/* * 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; } } }