Triggering Protected Model Method

Some important snippets, shortcuts and workarounds to remember that can do wonders at the right places.

Triggering Protected Model Method

Postby red1 » Wed May 03, 2017 10:13 am

As often said, I do not touch core to avoid creeping bugs, minimal maintenance and easy migration from version to version of iDempiere.
The Ninja plugin is doing wonders in bulk CSV import of model data such as for M_Product. However there is a protected AfterSave method that will generate Accounting Defaults and M_Cost detail. Those have to be done manually if we import data externally and not thru the MProduct.java.

To avoid redoing, or calling every core model Java method from my Ninja code, i have to externally invoke the afterSave() method of any model class during runtime dynamically. I faced an issue since last week as the methods are protected. After some long Googling and experimenting i finally made them work. Below is the very short snippet that can work on any core table:

Code: Select all
            if (tableHasSequence!=null){
               int pk = DB.getSQLValue(trxName, "SELECT "+model+"_ID FROM "+model+WHERE);
               if (pk>0){
                  PO po = tableHasSequence.getPO(pk, trxName);
                  Class<? extends PO> clazz = po.getClass();
                  try {
                     Method method = clazz.getDeclaredMethod("afterSave", boolean.class,boolean.class);
                     method.setAccessible(true);
                     method.invoke(po, true,true);
                  } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                        | NoSuchMethodException | SecurityException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                  }

As seen, there is no fixed model identity. TableHasSequence is the PO model of the core data i.e. M_Product or C_BPartner. All we need to do is to extract from the ModelFactory and pass it the primary key (PK) of the record just created from a CSV line.

The tricky part is to get the right POClass to expose its protected method. The hack is method.setAccessible(true). That is a good problem as it means we can hack into protected Java code! :)

Finally invoke must use the PO instance and passing the two args of (new record?, successful save?).

This snipper is part of the HandleCSVImport class and at the end of the everyLineUpdateInsert(). I will test if for the updateExisted method too. Then will try it for all the core models importing. Once OK will commit it.
red1
Site Admin
 
Posts: 2706
Joined: Tue Jul 06, 2004 3:01 pm
Location: Kuala Lumpur, Malaysia

Re: Triggering Protected Model Method

Postby red1 » Thu May 04, 2017 9:04 am

There were errors due to wrongfully assuming the model_ID construct, and so the following is final, more neat and i did for beforeSave too. The exception is left alone as it does not matter if no such method exists.

Code: Select all
   private void beforeAfterSaveModels(String whereClause, boolean newRecord) {
      PO po = new Query(Env.getCtx(),model,whereClause,trxName)
            .first();
      if (po==null)
         return;
      
      Class<? extends PO> clazz = po.getClass();
      if (clazz==null)
         return;
         
      try {
         Method method = clazz.getDeclaredMethod("beforeSave", boolean.class);
         method.setAccessible(true);
         method.invoke(po,newRecord);
      } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
            | NoSuchMethodException | SecurityException e) {
         // Doesn't matter
      }
      try {
         Method method = clazz.getDeclaredMethod("afterSave", boolean.class,boolean.class);
         method.setAccessible(true);
         method.invoke(po, newRecord,true);
      } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
            | NoSuchMethodException | SecurityException e) {
         // Doesn't matter
      }
red1
Site Admin
 
Posts: 2706
Joined: Tue Jul 06, 2004 3:01 pm
Location: Kuala Lumpur, Malaysia

Re: Triggering Protected Model Method

Postby red1 » Sat May 06, 2017 5:17 pm

You wouldn't believe this, but the even more final solution is too simple. All it needs is just MTablePO.getPO(0,trx) to construct the PO for a new record and to impose the po.set_Value(columnname, value), ending with a simple po.saveEx(trxname).

Code: Select all
//new PO .. user ModelFactory Constructor
MTable tableHasSequence = new Query(Env.getCtx(),MTable.Table_Name,MTable.COLUMNNAME_TableName+"=?",trxName)
            .setParameters(model).first();
PO po =null; 
if (tableHasSequence!=null){
   po = tableHasSequence.getPO(0, trxName);
}
But the PO is strict about Datatypes, so we have to explicitly set for Timestamp and BigDecimal:
Code: Select all
//Set_Value
if (po!=null){
   po.set_ValueOfColumn(SQLcols.get(i), SQLcols.get(i).endsWith("_ID")?lookupid:data[i]);
   MColumn col = new Query(Env.getCtx(),MColumn.Table_Name,MColumn.COLUMNNAME_ColumnName+"=?",trxName)
         .setParameters(SQLcols.get(i))
         .first();
   if (col.getAD_Reference_ID()==15 || col.getAD_Reference_ID()==16){
         StringBuilder date = new StringBuilder(data[i]);
         if (date.length()<12)
            date = date.append(" 00:00:00");
         po.set_ValueOfColumn(col.getColumnName(),Timestamp.valueOf(date.toString()))
      }
      if (col.getAD_Reference_ID()==12 ||col.getAD_Reference_ID()==29){
               po.set_ValueOfColumn(col.getColumnName(),new BigDecimal(data[i]));
      }

Code: Select all
//Save will trigger M[class] before/after Save methods
if (po!=null){
      po.saveEx(trxName);   
}
It does trigger the MOrderLine beforeSave() and afterSave() methods.
Now a C_OrderLine template can be as short as this! :

Code: Select all
C_Order_ID,M_Product_ID,DateOrdered,QtyEntered
2222,Transplanter,2016-01-22,2
red1
Site Admin
 
Posts: 2706
Joined: Tue Jul 06, 2004 3:01 pm
Location: Kuala Lumpur, Malaysia


Return to Good Artists Copy

Who is online

Users browsing this forum: No registered users and 1 guest

cron