package io.cattle.platform.extension.impl; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.extension.ExtensionImplementation; import io.cattle.platform.extension.ExtensionManager; import io.cattle.platform.extension.ExtensionPoint; import io.cattle.platform.util.type.CollectionUtils; import io.cattle.platform.util.type.InitializationTask; import io.cattle.platform.util.type.NamedUtils; import io.cattle.platform.util.type.PriorityUtils; import io.cattle.platform.util.type.ScopeUtils; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; public class ExtensionManagerImpl implements ExtensionManager, InitializationTask { private static final String WILDCARD = "regexp:"; Map<String, List<Object>> byKeyRegistry = new HashMap<String, List<Object>>(); Map<String, ExtensionList<Object>> extensionLists = new HashMap<String, ExtensionList<Object>>(); Map<String, ExtensionMap<String, Object>> extensionMaps = new HashMap<>(); Map<String, List<Object>> byName = new HashMap<String, List<Object>>(); Map<Object, String> objectToName = new HashMap<Object, String>(); Map<String, Class<?>> keyToType = new HashMap<String, Class<?>>(); Map<Pattern, List<String>> wildcards = new HashMap<Pattern, List<String>>(); boolean started = false; @SuppressWarnings("unchecked") @Override public <T> T first(String key, String typeString) { try { Class<?> clz = Class.forName(typeString); return first(key, (Class<T>) clz); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Failed to find class [" + typeString + "]", e); } } @SuppressWarnings("unchecked") @Override public <T> T first(String key, Class<T> type) { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, new FirstInstanceInvocationHandler(getExtensionListInternal(key))); } @Override public List<?> list(String key) { return getExtensionListInternal(key); } @Override public Map<String, Object> map(String key) { return getExtensionMapInternal(key); } protected synchronized ExtensionMap<String, Object> getExtensionMapInternal(String key) { ExtensionMap<String, Object> map = extensionMaps.get(key); if (map == null) { Map<String, Object> inner = started ? getMap(key) : Collections.<String, Object>emptyMap(); map = new ExtensionMap<>(this, key, inner); extensionMaps.put(key, map); } return map; } @Override public <T> List<T> getExtensionList(Class<T> type) { return getExtensionList(ScopeUtils.getScopeFromClass(type), type); } @SuppressWarnings("unchecked") @Override public <T> List<T> getExtensionList(String key, Class<T> type) { Class<?> clz = keyToType.get(key); if (type != null && clz != null && clz != type) { throw new IllegalArgumentException("Extension list for key [" + key + "] is of type [" + type + "] got [" + clz + "]"); } return (List<T>) getExtensionListInternal(key); } protected synchronized ExtensionList<?> getExtensionListInternal(String key) { ExtensionList<Object> list = extensionLists.get(key); if (list == null) { List<Object> inner = started ? getList(key) : Collections.emptyList(); list = new ExtensionList<Object>(this, key, inner); extensionLists.put(key, list); } return list; } public synchronized void addObject(String key, Class<?> clz, Object obj, String name) { Class<?> existing = keyToType.get(key); if (existing == null) { keyToType.put(key, clz); } else if (existing != clz) { throw new IllegalArgumentException("Can not change type of key [" + key + "] to [" + clz + "] already [" + existing + "]"); } List<Object> objects = byKeyRegistry.get(key); if (objects == null) { objects = new CopyOnWriteArrayList<Object>(); byKeyRegistry.put(key, objects); } objects.add(obj); CollectionUtils.addToMap(byName, name, obj, ArrayList.class); objectToName.put(obj, name); Pattern pattern = null; if (key.startsWith(WILDCARD)) { pattern = Pattern.compile(key.substring(0, WILDCARD.length())); } else if (key.contains("*")) { String[] parts = StringUtils.splitByWholeSeparator(key.trim(), "*"); for (int i = 0; i < parts.length; i++) { parts[i] = Pattern.quote(parts[i].trim()); } pattern = Pattern.compile(parts.length == 0 ? ".*" : StringUtils.join(parts, ".*")); } if (pattern != null) { CollectionUtils.addToMap(wildcards, pattern, name, ArrayList.class); } } @Override public synchronized void start() { if (!started) { for (Map.Entry<String, List<Object>> entry : byKeyRegistry.entrySet()) { String key = entry.getKey(); ExtensionList<?> extensionList = getExtensionListInternal(key); extensionList.inner.clear(); extensionList.inner.addAll(getList(key)); ExtensionMap<String, Object> extensionMap = getExtensionMapInternal(key); extensionMap.inner.clear(); extensionMap.inner.putAll(getMap(key)); } started = true; } } protected synchronized Map<String, Object> getMap(String key) { List<?> list = getList(key); Map<String, Object> map = new ConcurrentHashMap<>(); for (Object item : list) { map.put(NamedUtils.getName(item), item); } return map; } public void reset() { started = false; start(); } protected synchronized List<Object> getList(String key) { Class<?> typeClz = keyToType.get(key); if (typeClz == null) { typeClz = Object.class; } Set<String> excludes = getSetting(key + ".exclude"); String list = ArchaiusUtil.getString(key + ".list").get(); if (!StringUtils.isBlank(list)) { List<Object> result = new ArrayList<Object>(); for (String name : list.split("\\s*,\\s*")) { if (excludes.contains(name)) { continue; } result.addAll(getObjectsByName(name, typeClz)); } return result; } Set<String> includes = getSetting(key + ".include"); Set<Object> ordered = new TreeSet<Object>(new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { int left = PriorityUtils.getPriority(o1); int right = PriorityUtils.getPriority(o2); if (left < right) { return -1; } else if (left > right) { return 1; } String leftName = objectToName.get(o1); String rightName = objectToName.get(o2); int comparisonResult = leftName.compareTo(rightName); if (comparisonResult == 0 && !o1.equals(o2)) { throw new RuntimeException("Trying to add 2 objects with the same name: " + leftName + ". Second object is ignored!"); } return comparisonResult; } }); List<?> registered = byKeyRegistry.get(key); if (registered != null) { ordered.addAll(registered); } ordered.addAll(getByWildcard(key, typeClz)); for (String include : includes) { ordered.addAll(getObjectsByName(include, typeClz)); } Iterator<Object> iter = ordered.iterator(); while (iter.hasNext()) { String name = objectToName.get(iter.next()); if (excludes.contains(name)) { iter.remove(); } } return new ArrayList<Object>(ordered); } protected List<Object> getByWildcard(String key, Class<?> typeClz) { List<Object> result = new ArrayList<Object>(); for (Map.Entry<Pattern, List<String>> entry : wildcards.entrySet()) { if (entry.getKey().matcher(key).matches()) { for (String name : entry.getValue()) { result.addAll(getObjectsByName(name, typeClz)); } } } return result; } protected List<Object> getObjectsByName(String name, Class<?> typeClz) { List<Object> result = new ArrayList<Object>(); List<Object> objs = byName.get(name); if (objs != null) { for (Object obj : objs) { if (typeClz.isAssignableFrom(obj.getClass())) { result.add(obj); } } } return result; } protected Set<String> getSetting(String key) { String value = getSettingValue(key); if (StringUtils.isBlank(value)) { return Collections.emptySet(); } Set<String> result = new HashSet<String>(); for (String part : value.trim().split("\\s*,\\s*")) { result.add(part); } return result; } protected String getSettingValue(String key) { return ArchaiusUtil.getString(key).get(); } @Override public List<ExtensionPoint> getExtensions() { List<ExtensionPoint> result = new ArrayList<ExtensionPoint>(); Set<String> keys = new TreeSet<String>(extensionLists.keySet()); for (String key : keys) { result.add(getExtensionPoint(key)); } if (keys.size() != extensionLists.size()) { /* * While traversing the extensions, more extension might be * registered so try again */ return getExtensions(); } return result; } @Override public ExtensionPoint getExtensionPoint(Class<?> type) { return getExtensionPoint(ScopeUtils.getScopeFromClass(type), type); } @Override public ExtensionPoint getExtensionPoint(String key, Class<?> type) { Class<?> clz = keyToType.get(key); if (clz != null && clz != type) { throw new IllegalArgumentException("Extension list for key [" + key + "] is of type [" + type + "] got [" + clz + "]"); } return getExtensionPoint(key); } protected ExtensionPoint getExtensionPoint(String key) { List<ExtensionImplementation> impls = new ArrayList<ExtensionImplementation>(); List<?> list = getExtensionList(key, null); if (list != null) { for (Object obj : list) { String name = objectToName.get(obj); if (name == null) { name = "Dynamic : " + NamedUtils.getName(obj); } impls.add(new ExtensionImplementationImpl(name, obj)); } } return new ExtensionPointImpl(key, impls, getSettingValue(key + ".list"), getSettingValue(key + ".exclude"), getSettingValue(key + ".include")); } protected Class<?> getExpectedType(String key) { return keyToType.get(key); } }