IDEMPIERE-5004 add org.adempiere.base.Model annotation support to MappedModelFactory (#930)
This commit is contained in:
parent
a73ce05875
commit
4b923f4d03
|
@ -29,6 +29,7 @@ import java.util.function.BiFunction;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.compiere.model.PO;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -54,4 +55,10 @@ public interface IMappedModelFactory {
|
|||
*/
|
||||
void removeMapping(String tableName);
|
||||
|
||||
/**
|
||||
* Scan packages for class with {@link org.adempiere.base.Model} annotation and add mapping for it
|
||||
* @param context
|
||||
* @param packages
|
||||
*/
|
||||
public void scan(BundleContext context, String... packages);
|
||||
}
|
|
@ -24,15 +24,30 @@
|
|||
**********************************************************************/
|
||||
package org.idempiere.model;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.adempiere.base.IModelFactory;
|
||||
import org.adempiere.base.Model;
|
||||
import org.compiere.model.PO;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.Env;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.wiring.BundleWiring;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
import io.github.classgraph.AnnotationInfo;
|
||||
import io.github.classgraph.ClassGraph;
|
||||
import io.github.classgraph.ClassInfo;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
import io.github.classgraph.ScanResult;
|
||||
|
||||
/**
|
||||
* @author hengsin
|
||||
*
|
||||
|
@ -47,6 +62,8 @@ public class MappedModelFactory implements IModelFactory, IMappedModelFactory {
|
|||
private final ConcurrentHashMap<String, BiFunction<Integer, String, ? extends PO>> recordIdMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, BiFunction<ResultSet, String, ? extends PO>> resultSetMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static final CLogger s_log = CLogger.getCLogger(MappedModelFactory.class);
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
*/
|
||||
|
@ -85,4 +102,93 @@ public class MappedModelFactory implements IModelFactory, IMappedModelFactory {
|
|||
recordIdMap.remove(tableName);
|
||||
resultSetMap.remove(tableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scan(BundleContext context, String... packages) {
|
||||
ClassLoader classLoader = context.getBundle().adapt(BundleWiring.class).getClassLoader();
|
||||
|
||||
ClassGraph graph = new ClassGraph()
|
||||
.enableAnnotationInfo()
|
||||
.overrideClassLoaders(classLoader)
|
||||
.disableNestedJarScanning()
|
||||
.disableModuleScanning()
|
||||
.acceptPackagesNonRecursive(packages);
|
||||
|
||||
try (ScanResult scanResult = graph.scan())
|
||||
{
|
||||
|
||||
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Model.class)) {
|
||||
if (classInfo.isAbstract())
|
||||
continue;
|
||||
String className = classInfo.getName();
|
||||
AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(Model.class);
|
||||
String tableName = (String) annotationInfo.getParameterValues().getValue("table");
|
||||
|
||||
// find subclass (if any)
|
||||
ClassInfoList subclasses = classInfo.getSubclasses().directOnly();
|
||||
while(!subclasses.isEmpty()) {
|
||||
className = subclasses.get(0).getName();
|
||||
subclasses = subclasses.get(0).getSubclasses().directOnly();
|
||||
}
|
||||
|
||||
try {
|
||||
final Class<?> clazz = classLoader.loadClass(className);
|
||||
Supplier<Class<?>> classSupplier = () -> { return clazz; };
|
||||
Constructor<?> idConstructor = clazz.getDeclaredConstructor(new Class[]{Properties.class, int.class, String.class});
|
||||
RecordIdFunction recordIdFunction = new RecordIdFunction(idConstructor);
|
||||
Constructor<?> rsConstructor = clazz.getDeclaredConstructor(new Class[]{Properties.class, ResultSet.class, String.class});
|
||||
ResultSetFunction resultSetFunction = new ResultSetFunction(rsConstructor);
|
||||
addMapping(tableName, classSupplier, recordIdFunction, resultSetFunction);
|
||||
} catch (Exception e) {
|
||||
if (s_log.isLoggable(Level.INFO))
|
||||
s_log.log(Level.INFO, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RecordIdFunction implements BiFunction<Integer, String, PO> {
|
||||
private Constructor<?> constructor;
|
||||
|
||||
private RecordIdFunction(Constructor<?> constructor) {
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PO apply(Integer id, String trxName) {
|
||||
if (constructor != null) {
|
||||
try {
|
||||
return (PO) constructor.newInstance(Env.getCtx(), id, trxName);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
constructor = null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResultSetFunction implements BiFunction<ResultSet, String, PO> {
|
||||
private Constructor<?> constructor;
|
||||
|
||||
private ResultSetFunction(Constructor<?> constructor) {
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PO apply(ResultSet rs, String trxName) {
|
||||
if (constructor != null) {
|
||||
try {
|
||||
return (PO)constructor.newInstance(new Object[] {Env.getCtx(), rs, trxName});
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
constructor = null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.util.Properties;
|
|||
|
||||
import org.adempiere.base.Core;
|
||||
import org.adempiere.base.IModelFactory;
|
||||
import org.compiere.model.MColor;
|
||||
import org.compiere.model.MTable;
|
||||
import org.compiere.model.PO;
|
||||
import org.compiere.model.X_Test;
|
||||
|
@ -42,6 +43,7 @@ import org.idempiere.model.IMappedModelFactory;
|
|||
import org.idempiere.model.MappedModelFactory;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.TestActivator;
|
||||
import org.idempiere.test.model.annotated.MyAnnotatedColorModel;
|
||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -83,6 +85,16 @@ public class MappedModelFactoryTest extends AbstractTestCase {
|
|||
assertTrue(po instanceof MyTest2, "PO not instanceof MyTest2. PO.className="+po.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAnnotatedModelMapping() {
|
||||
BundleContext bc = TestActivator.context;
|
||||
Core.getMappedModelFactory().scan(bc, "org.idempiere.test.model.annotated");
|
||||
CacheMgt.get().reset();
|
||||
PO po = MTable.get(MColor.Table_ID).getPO(0, getTrxName());
|
||||
assertTrue(po instanceof MyAnnotatedColorModel, "PO not instanceof MyAnnotatedColorModel. PO.className="+po.getClass().getName());
|
||||
}
|
||||
|
||||
private final static class MyFactory extends MappedModelFactory {
|
||||
|
||||
public MyFactory() {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/***********************************************************************
|
||||
* 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.idempiere.test.model.annotated;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.compiere.model.MColor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
@org.adempiere.base.Model(table = MColor.Table_Name)
|
||||
public class MyAnnotatedColorModel extends MColor {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MyAnnotatedColorModel(Properties ctx, int Test_ID, String trxName) {
|
||||
super(ctx, Test_ID, trxName);
|
||||
}
|
||||
|
||||
public MyAnnotatedColorModel(Properties ctx, ResultSet rs, String trxName) {
|
||||
super(ctx, rs, trxName);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue