/*
* Copyright 2013-2016 EMC Corporation. 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.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file 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.emc.ecs.sync.util;
import com.emc.ecs.sync.SyncPlugin;
import com.emc.ecs.sync.config.SyncOptions;
import com.emc.ecs.sync.filter.InternalFilter;
import com.emc.ecs.sync.filter.SyncFilter;
import com.emc.ecs.sync.storage.SyncStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import java.lang.reflect.ParameterizedType;
import java.util.*;
public final class PluginUtil {
private static final Logger log = LoggerFactory.getLogger(PluginUtil.class);
private static ClassPathScanningCandidateComponentProvider pluginScanner;
static {
pluginScanner = new ClassPathScanningCandidateComponentProvider(false);
pluginScanner.addIncludeFilter(new AssignableTypeFilter(SyncPlugin.class));
pluginScanner.addExcludeFilter(new AnnotationTypeFilter(InternalFilter.class));
}
@SuppressWarnings("unchecked")
public synchronized static <P extends SyncPlugin<C>, C> Class<C> configClassFor(Class<P> pluginClass) {
try {
ParameterizedType storageType = (ParameterizedType) pluginClass.getGenericSuperclass();
return (Class<C>) storageType.getActualTypeArguments()[0];
} catch (ClassCastException e) {
log.warn("could not find config type for " + pluginClass.getSimpleName(), e);
return null;
}
}
public static <C> SyncStorage<C> newStorageFromConfig(C storageConfig, SyncOptions syncOptions) {
return (SyncStorage<C>) newPluginFromConfig(storageConfig, syncOptions);
}
public static List<SyncFilter> newFiltersFromConfigList(List<?> filterConfigs, SyncOptions syncOptions) {
List<SyncFilter> filters = new ArrayList<>();
for (Object filterConfig : filterConfigs) {
filters.add(newFilterFromConfig(filterConfig, syncOptions));
}
return filters;
}
public static <C> SyncFilter<C> newFilterFromConfig(C filterConfig, SyncOptions syncOptions) {
return (SyncFilter<C>) newPluginFromConfig(filterConfig, syncOptions);
}
@SuppressWarnings("unchecked")
private static <P extends SyncPlugin<C>, C> P newPluginFromConfig(C pluginConfig, SyncOptions syncOptions) {
try {
if (pluginCache.isEmpty()) loadPluginCache();
Class pClass = pluginCache.get(pluginConfig.getClass());
if (pClass == null)
throw new IllegalArgumentException("No plugin found that uses " + pluginConfig.getClass().getSimpleName());
P plugin = (P) pClass.newInstance();
plugin.setConfig(pluginConfig);
plugin.setOptions(syncOptions);
return plugin;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static Map<Class, Class> pluginCache = new HashMap<>();
private static synchronized void loadPluginCache() {
if (pluginCache.isEmpty()) {
for (Class<? extends SyncPlugin> pClass : allPluginClasses()) {
pluginCache.put(configClassFor(pClass), pClass);
}
}
}
private static Iterable<Class<? extends SyncPlugin>> allPluginClasses() {
return new Iterable<Class<? extends SyncPlugin>>() {
@Override
public Iterator<Class<? extends SyncPlugin>> iterator() {
return new PluginClassIterator(pluginScanner.findCandidateComponents("com.emc.ecs.sync").iterator());
}
};
}
private PluginUtil() {
}
private static class PluginClassIterator extends ReadOnlyIterator<Class<? extends SyncPlugin>> {
private Iterator<BeanDefinition> delegate;
PluginClassIterator(Iterator<BeanDefinition> delegate) {
this.delegate = delegate;
}
@SuppressWarnings("unchecked")
@Override
protected Class<? extends SyncPlugin> getNextObject() {
while (delegate.hasNext()) {
BeanDefinition beanDef = delegate.next();
try {
return (Class<? extends SyncPlugin>) Class.forName(beanDef.getBeanClassName());
} catch (ClassNotFoundException e) {
log.warn("the {} plugin cannot be found: {}", beanDef.getBeanClassName(), e.toString());
log.debug("stacktrace:", e);
} catch (UnsupportedClassVersionError e) {
String plugin = e.getMessage();
try {
plugin = plugin.substring(plugin.indexOf("Provider ") + 9).split(" ")[0];
plugin = plugin.substring(plugin.lastIndexOf(".") + 1);
} catch (Throwable t) {
// ignore
}
log.warn("the {} plugin is not supported in this version of java", plugin);
log.debug("stacktrace:", e);
}
}
return null;
}
}
}