/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.adempiere.base;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.adempiere.base.annotation.Process;
import org.compiere.process.ProcessCall;
import org.compiere.util.CLogger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import io.github.classgraph.AnnotationInfo;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassGraph.ScanResultProcessor;
import io.github.classgraph.ClassInfo;
/**
* Scan, discover and register process classes.
* Process class will be registered using class name. You can use the optional
* {@link Process} annotation to register a process class with an additional name (for e.g
* to replace a core process class).
* @author hengsin
*
*/
public abstract class AnnotationBasedProcessFactory extends AnnotationBasedFactory implements IProcessFactory
{
/**
* Name to class cache
*/
private final Map classCache = new HashMap<>();
private final Map[]> constructorCache = new ConcurrentHashMap<>();
private BundleContext bundleContext = null;
private final static CLogger s_log = CLogger.getCLogger(AnnotationBasedProcessFactory.class);
/**
* Subclasses must override this method in order to provide packages to
* scan, discover and register process classes
* @return array of packages to be accepted during class scanning
* @see ClassGraph#acceptPackagesNonRecursive(String...)
*/
protected abstract String[] getPackages();
/**
* Scan annotation upon activation of component
* @param context
* @throws ClassNotFoundException
*/
@Activate
public void activate(ComponentContext context) throws ClassNotFoundException {
long start = System.currentTimeMillis();
bundleContext = context.getBundleContext();
ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
ClassGraph graph = new ClassGraph()
.enableAnnotationInfo()
.overrideClassLoaders(classLoader)
.disableNestedJarScanning()
.disableModuleScanning();
// narrow search to a list of packages
String[] packages = getPackages();
graph.acceptPackagesNonRecursive(packages);
ScanResultProcessor scanResultProcessor = scanResult -> {
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Process.class)) {
if (classInfo.isAbstract())
continue;
String className = classInfo.getName();
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(Process.class);
String alternateName = null;
if (annotationInfo != null)
alternateName = (String) annotationInfo.getParameterValues().getValue("name");
classCache.put(className, className);
if (alternateName != null)
classCache.put(alternateName, className);
}
long end = System.currentTimeMillis();
if (s_log.isLoggable(Level.INFO))
s_log.info(() -> this.getClass().getSimpleName() + " loaded " + classCache.size() + " classes in "
+ ((end-start)/1000f) + "s");
signalScanCompletion(true);
};
graph.scanAsync(getExecutorService(), getMaxThreads(), scanResultProcessor, getScanFailureHandler());
}
@SuppressWarnings("unchecked")
@Override
public ProcessCall newProcessInstance(String className) {
blockWhileScanning();
ProcessCall pc = null;
String realClassName = classCache.get(className);
if (realClassName != null) {
Constructor>[] constructors = constructorCache.get(realClassName);
if (constructors == null) {
Class extends ProcessCall> clazz = null;
try {
ClassLoader classLoader = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
clazz = (Class extends ProcessCall>) classLoader.loadClass(realClassName);
Constructor extends ProcessCall> constructor = clazz.getDeclaredConstructor();
if (constructor != null) {
constructors = new Constructor[] {constructor};
constructorCache.put(realClassName, constructors);
}
} catch (Exception e) {
s_log.log(Level.WARNING, e.getMessage(), e);
}
if (constructors == null)
constructorCache.put(realClassName, new Constructor[0]);
}
if (constructors != null && constructors.length == 1) {
try {
pc = (ProcessCall) constructors[0].newInstance();
} catch (Exception e) {
s_log.log(Level.WARNING, e.getMessage(), e);
constructorCache.put(realClassName, new Constructor[0]);
}
}
}
return pc;
}
}