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 java.util.function.Supplier;
|
||||||
|
|
||||||
import org.compiere.model.PO;
|
import org.compiere.model.PO;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -54,4 +55,10 @@ public interface IMappedModelFactory {
|
||||||
*/
|
*/
|
||||||
void removeMapping(String tableName);
|
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;
|
package org.idempiere.model;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.adempiere.base.IModelFactory;
|
import org.adempiere.base.IModelFactory;
|
||||||
|
import org.adempiere.base.Model;
|
||||||
import org.compiere.model.PO;
|
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 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
|
* @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<Integer, String, ? extends PO>> recordIdMap = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, BiFunction<ResultSet, String, ? extends PO>> resultSetMap = 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
|
* default constructor
|
||||||
*/
|
*/
|
||||||
|
@ -85,4 +102,93 @@ public class MappedModelFactory implements IModelFactory, IMappedModelFactory {
|
||||||
recordIdMap.remove(tableName);
|
recordIdMap.remove(tableName);
|
||||||
resultSetMap.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.Core;
|
||||||
import org.adempiere.base.IModelFactory;
|
import org.adempiere.base.IModelFactory;
|
||||||
|
import org.compiere.model.MColor;
|
||||||
import org.compiere.model.MTable;
|
import org.compiere.model.MTable;
|
||||||
import org.compiere.model.PO;
|
import org.compiere.model.PO;
|
||||||
import org.compiere.model.X_Test;
|
import org.compiere.model.X_Test;
|
||||||
|
@ -42,6 +43,7 @@ import org.idempiere.model.IMappedModelFactory;
|
||||||
import org.idempiere.model.MappedModelFactory;
|
import org.idempiere.model.MappedModelFactory;
|
||||||
import org.idempiere.test.AbstractTestCase;
|
import org.idempiere.test.AbstractTestCase;
|
||||||
import org.idempiere.test.TestActivator;
|
import org.idempiere.test.TestActivator;
|
||||||
|
import org.idempiere.test.model.annotated.MyAnnotatedColorModel;
|
||||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
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());
|
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 {
|
private final static class MyFactory extends MappedModelFactory {
|
||||||
|
|
||||||
public MyFactory() {
|
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