IDEMPIERE-5025 Easier usage of process parameters (#963)

* IDEMPIERE-5025 Easier usage of process parameters

Created @Parameter annotation in order to tag process class attributes
as parameters and have them to have their values assigned automatically
without extra code.

* Minor changes
This commit is contained in:
Saulo José Gil 2021-11-06 03:50:14 -03:00 committed by GitHub
parent 96f34baad4
commit 4d93067d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 0 deletions

View File

@ -0,0 +1,51 @@
/******************************************************************************
* Product: iDempiere ERP & CRM Smart Business Solution *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
*****************************************************************************/
package org.adempiere.base.annotation;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.compiere.model.X_AD_Process_Para;
import org.compiere.process.SvrProcess;
/**
* Tags a process class field as a process parameter in order to have its value set automatically.
* Class fields are matched against process parameters using the following heuristics: <br>
* [1] If the parameter annotation has a name, then it must match exactly the process parameter
* metadata definition. For example: <br>
* <code>@Parameter(name="C_BPartner_ID") int foo</code> will fill <code>foo</code> with
* the value of the parameter named <code>C_BPartner_ID</code>. <br>
* [2] Class fields with the <code>p_</code> prefix will be matched automatically. Example: <br>
* <code>@Parameter Integer p_C_BPartner_ID</code> will match a parameter named <code>C_BPartner_ID</code>.
* [3] Fields with their names matching metadata names after stripping the "_" character. Example:
* <code>@Parameter Integer cBPartnerId</code> will match a parameter named <code>C_BPartner_ID</code>.
* [4] Fields with their names matching exactly their metadata names. Example:
* <code>@Parameter Integer C_BPartner_ID</code> will match a parameter named <code>C_BPartner_ID</code>.
* @see SvrProcess
* @author Saulo Gil
*/
@Target(FIELD)
@Retention(RUNTIME)
public @interface Parameter {
/**
* Optional parameter name matching its metadata definition.
* @see X_AD_Process_Para#getColumnName()
* @return
*/
String name() default "";
}

View File

@ -16,17 +16,25 @@
*****************************************************************************/
package org.compiere.process;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.adempiere.base.annotation.Parameter;
import org.adempiere.base.event.EventManager;
import org.adempiere.base.event.EventProperty;
import org.adempiere.base.event.IEventManager;
@ -234,6 +242,7 @@ public abstract class SvrProcess implements ProcessCall
boolean success = true;
try
{
autoFillParameters();
prepare();
// event before process
@ -719,4 +728,92 @@ public abstract class SvrProcess implements ProcessCall
processUI.statusUpdate(message);
}
}
/**
* Attempts to initialize class fields having the {@link Parameter} annotation
* with the values received by this process instance.
*/
private void autoFillParameters(){
Map<String,Field> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// detects annotated fields in this class and its super classes
Class<?> target = getClass();
while(target != null && !target.equals(SvrProcess.class)) {
for (Field field: getFieldsWithParameters(target)) {
field.setAccessible(true);
Parameter pa = field.getAnnotation(Parameter.class);
if(map.containsValue(field))
continue;
String name = pa.name().isEmpty() ? field.getName() : pa.name();
map.put(name.toLowerCase(), field);
}
target = target.getSuperclass();
}
if(map.size()==0)
return;
for(ProcessInfoParameter parameter : getParameter()){
String name = parameter.getParameterName().trim().toLowerCase();
Field field = map.get(name);
Field toField = map.containsKey(name + "_to") ? map.get(name + "_to") : null;
// try to match fields using the "p_" prefix convention
if(field==null) {
String candidate = "p_" + name;
field = map.get(candidate);
toField = map.containsKey(candidate + "_to") ? map.get(candidate + "_to") : null;
}
// try to match fields with same name as metadata declaration after stripping "_"
if(field==null) {
String candidate = name.replace("_", "");
field = map.get(candidate);
toField = map.containsKey(candidate + "to") ? map.get(candidate + "to") : null;
}
if(field==null)
continue;
Type type = field.getType();
try{
if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {
field.set(this, parameter.getParameterAsInt());
if(parameter.getParameter_To()!=null && toField != null)
toField.set(this, parameter.getParameter_ToAsInt());
} else if (type.equals(String.class)) {
field.set(this, (String) parameter.getParameter());
} else if (type.equals(java.sql.Timestamp.class)) {
field.set(this, (Timestamp) parameter.getParameter());
if(parameter.getParameter_To()!=null && toField != null)
toField.set(this, (Timestamp) parameter.getParameter_To());
} else if (type.equals(BigDecimal.class)) {
field.set(this, (BigDecimal) parameter.getParameter());
} else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
Object tmp = parameter.getParameter();
if(tmp instanceof String && tmp != null)
field.set(this, "Y".equals(tmp));
} else {
continue;
}
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
/**
* Tries to find all class attributes having the {@link Parameter} annotation.
* @param clazz
* @return a list of annotated fields
*/
private List<Field> getFieldsWithParameters(Class<?> clazz) {
if (clazz != null)
return Arrays.stream(clazz.getDeclaredFields())
.filter(f -> f.getAnnotation(Parameter.class) != null)
.collect(Collectors.toList());
return Collections.emptyList();
}
} // SvrProcess