Extending a Model

1,898 views
Skip to first unread message

Chuck Boecking

unread,
Mar 25, 2014, 4:56:57 PM3/25/14
to idem...@googlegroups.com
Hi Everyone,

I would like your advise on a topic. Here are the details:
  • I am creating a plugin.
  • The functionality requires that I add new fields to the Order table.
  • I would like to extend MOrder (for example: MOrderChuBoe extends MOrder) and give MOrderChuBoe access to the new c_order field getters and setters.
My question is: what is the easiest and best way of doing that? I do not think that IModelFactory is the answer. Here are my reasons. Let me know if I am wrong:
  1. Only the plugin needs to access MOrderChuBoe. Because it will be in the same plugin, there is no need for an IModelFactory. Said another way, I do not care if the system uses the MOrder class (as opposed to the MOrderChuBoe class) when it saves an order. 
  2. I am concerned about using IModelFactory in general because another plugin with a IModelFactory with a better service priority for C_Order could steal my thunder. 
  3. I think the only time I would need to use IModelFactory is when other plugins need access to my MOrderChuBoe class.
I think the best option is to do the following:
  1. Use the model.generator to create my X_ and I_ classes for C_Order in some random temp folder. 
  2. Copy the needed getters and setters from my newly created X_ class and paste them into MOrderChuBoe.
  3. Doing so would my plugin all the features and functionality of MOrder plus the convenience of the new field getters and setters (all from the same MOrderChuBoe class).
Thank you for reading and offering your opinion!

Regards,

Chuck Boecking

Carlos Antonio Ruiz Gomez

unread,
Mar 25, 2014, 6:04:19 PM3/25/14
to idem...@googlegroups.com
Hi Chuck, if your intention is to trigger the before/after methods from a window - then I would advice better to use a model validator for core tables.

But if your intention is just to add a business method that will be used inside your plugin then you can extend MOrder and use your own class within your plugin (like LCO_MInvoice)

Another approach is to use generic setters/getters from PO for your custom columns.  Like get_Value and set_ValueOfColumn

Another approach is to generate with the model generator just the interface I_ and use the interface instead of the MOrderChuBoe on your custom code.  You can use the getters and setters from the interface and the PO class will do the work.

Regards,

Carlos Ruiz

Heng Sin Low

unread,
Mar 25, 2014, 7:31:43 PM3/25/14
to idem...@googlegroups.com
Hi Chuck,

What we usually do here is to generate the I_ classes and use POWrapper to access the custom field added.

Regards,
Low


--
You received this message because you are subscribed to the Google Groups "iDempiere" group.
To unsubscribe from this group and stop receiving emails from it, send an email to idempiere+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/idempiere/542504cd-ff88-49e5-b7c8-fbacb73c14dc%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jan Thielemann

unread,
Mar 26, 2014, 5:52:49 AM3/26/14
to idem...@googlegroups.com
Hi Hengsin, can you give me some explaination on the POWrapper and how it is intended? 

Hi Chuck, if you use your model only in your plugin i don't see a problem with extending the base model and irgnoring the model factory. However, if you wan't to use the Query-class for example, you will get the default PO and if you try to cast it to your own model you will get a class cast exception. What you can do then is query e. g. MOrder objects and then load your model by the original POs id (new MOrderChuck(ctx, morder.getID(),trx)). Dirty but it will work.

Chuck Boecking

unread,
Mar 26, 2014, 9:23:49 PM3/26/14
to idem...@googlegroups.com
Hi Jan,

Here is an example of POWrapper that I found from a previous post. It seems straight forward. I will try it this evening.


Regards, Chuck

Chuck Boecking

unread,
Mar 26, 2014, 11:02:55 PM3/26/14
to idem...@googlegroups.com
Thanks everyone for your advice. I achieved success. Here were my steps for future reference:

  1. Followed these steps to create the iDempiere plugin (create plugin and start plugin)
  2. Created an iDempiere process called ValidateDocument. For now, it does nothing but look pretty.
  3. Added my new ValidationNotes field to C_Order Table and Column window and Order Window, Tab and Field window.
  4. Used the iDempiere model.generator (run configurations) to create my interface with my new field(s). NOTE: I actually created eight new fields on C_Order. That is why I used the model.generator instead of just creating the Interface by hand.
    1. model.validator
      1. Path = src folder of my plugin project
      2. Package = com.chuboe.docval.model
      3. Table = C_Order
      4. Entity Type = D
      5. Only generated the interface
    2. Renamed the newly created interface to I_C_Order_DocVal and set to extend I_C_Order
    3. Removed all but the newly created columns from I_C_Order_DocVal
  5. Updated my process to use the newly created I_C_Order_DocVal interface with a POWrapper as illustrated here. Tested with success.
Here was my interface (only showing the one field):

public interface I_C_Order_DocVal extends org.compiere.model.I_C_Order 
{
    /** Column name ValidationNotes */
    public static final String COLUMNNAME_ValidationNotes = "ValidationNotes";

/** Set Validation Notes.
  * Feedback from the last validation. If a given line states "Hard Fail", then the document action cannot proceed unless Validation Override is checked.
  */
public void setValidationNotes (String ValidationNotes);

/** Get Validation Notes.
  * Feedback from the last validation. If a given line states "Hard Fail", then the document action cannot proceed unless Validation Override is checked.
  */
public String getValidationNotes();
}

Here was my process doIt method. Note that is makes use of both the MOrder and I_C_Order_DocVal classes in conjunction (see the save method).

protected String doIt() throws Exception {
MOrder order = new MOrder(getCtx(), getRecord_ID(), get_TrxName());
I_C_Order_DocVal order_dv = POWrapper.create(order, I_C_Order_DocVal.class);
if (order_dv != null)
{
order_dv.setValidationNotes("Simply Wonderful"); //new field
order_dv.setDescription("Simply Wonderful"); //existing field set through POWrapper
order.saveEx(); //called on MOrder object to save (not POWrapper)
}
return "OK";
}

Regards,
Chuck Boecking

Chuck Boecking

unread,
Dec 10, 2014, 11:28:52 AM12/10/14
to idem...@googlegroups.com
Adding link to a related conversation for future reference:

Jan Thielemann

unread,
Dec 16, 2014, 9:10:49 AM12/16/14
to idem...@googlegroups.com

Hi Chuck,
this is much effort for this simple case. Why not use plain MOrder object and use the genralized getter/setter to access your fields:
order.set_ValueOfColumn("ValidationNotes", "Simply Wonderful");
order.set_ValueOfColumn("Descriptionbla", "Simply Wonderful, too");

In my mind, you only need a custom model class in some cases:
-if you have custom business logic, use model hooks or have a custom document (but be aware that often it is a choice of yours weather to put your business logic in the model or elsewhere, e.g. a process, eventhandler, modelvalidator)
-if you want to use the Query class to load your POs
-if you want typesafe getter/setters

Notice that you can even use the GenericPO to access every field if you know the columnnames.

Subbu Subbu

unread,
Dec 5, 2019, 11:18:24 AM12/5/19
to iDempiere

Hi Jan,

I hv watched your video on the creation of the model classes from the following link https://www.youtube.com/watch?v=Pxi46BmIrTw.

I generated the model classes for one of our custom tables. But both the interface and the class are showing lots of bugs. I hve included the plugin dependencies in my manifest.

Am I doing anything wrong??
I am including a screenshot of the eclipse screen for the interface.




Regards
Subbu
Screenshot from 2019-12-05 15-48-30.png

Flo Boj

unread,
Dec 5, 2019, 1:58:57 PM12/5/19
to iDempiere
Just wanna say thx for providing more insights pushing the I-X-M - topic once again.
in fact I read highly interested for I have quite a related prob ratm !

Everybpdy have a good time !
Greets 
Florian

Andreas Sumerauer

unread,
Dec 6, 2019, 4:01:12 AM12/6/19
to iDempiere
Keep in mind that AFAIK it is not a good idea to replace MOrder systemwide with a derived class. 
The reason is that  iDempiere creates MOrder objects in some processes (e. g. the Create Sales Orders from Expense process) and also other third party plugins will most probably not know about your class.
IMO the cleanest solution possible at the moment is to use a helper class that provides the additional getters and setters (for data stored in custom Columns) as well as your business logic. 
This way you can anyway keep your stuff nicely in one place. I admit that is not a perfect solution though at least this implementation does not break compatibility with other existing or future idempiere processes or with third party plugins.  
class myOrderHelper{
 
public static Foo getFoo(MOrder mOrder){...}
 
public static void setFoo(MOrder mOrder, Foo foo){...}
 
public static void myLogic(MOrder mOrder, ...){...}
}  

Of course the problem does only emerge when your plugin supposes the all Orders it will ever see are DerivedMOrder objects. 
(An event handler might as well pass a base class object for reasons described above)

I would really love to have a static function that can create a derived PO from a base class PO. like:
class DerivedMOrder{
 
static DerivedMOrder get(MOrder mOrder){...}
}

This would allow to write clean and properly encapsulated code.
Unfortunately it is not possible to write that function because there are private members in MOrder that are not exposed via getter functions.
(same applies for all other PO derived classes)

Andreas

Heng Sin Low

unread,
Dec 6, 2019, 7:12:07 AM12/6/19
to idem...@googlegroups.com
Another commonly use approach is to create interface for your custom getter and setter and use POWrapper.create(po, yourInterface.class) to wrap the PO with your custom interface.

--
You received this message because you are subscribed to the Google Groups "iDempiere" group.
To unsubscribe from this group and stop receiving emails from it, send an email to idempiere+...@googlegroups.com.

Andreas Sumerauer

unread,
Dec 19, 2019, 2:34:37 AM12/19/19
to iDempiere
Has anybody tried to use Lombok? 
I allows to define extension methods for existing classes. 

Basically you could define a fake member method myFunction() that can then be used like:
mOrder.myFunction()

Of course extension functions still can only access the public api of the class. Still they should help to improve code readability.

Andreas
Reply all
Reply to author
Forward
0 new messages