Metasfresh developer hints


#1

The purpose of this topic shall be to share hints for metasfresh developers.


#2

See what annotations lombok offers

Since we have lombok in almost every project’s pom.xml
(and btw, feel freeh to add it, if it’s still missing in the project you currently work on),

So we annotate a private field with @Getter and @Setter and save the disk space that would otherwise be take up by boiler plate get and set methods


#3

Create custom/non-jasper report processes

With issue "#4587 we introduced an SPI called ExecuteReportStrategy

It enables PDF-creating code to be called via print-format".
And example use case: a user needed dunning-PDF that contains

  • the “actual” dunning document (-> normal jasper report),
  • with the respective archived invoice(s) concatenated to it (-> iterate invoices, load archived PDF-byte[]s).

It’s now possible to write an AD_Process for this, and assign that process to an AD_Printformat. That way, the process can be invoked from both C_DocTypes and C_DocOutbound_Configs

To write your own such process, you can

  • implement you own ExecuteReportStrategy
  • subclass ReportStarter (which is now abstract btw)
  • implement ReportStarters abstract getExecuteReportStrategy() method

#4

See what de.metas.util.collections.CollectionUtils offers

Two example use cases:

  • you have a list and want to assert that there is exactly one element in it, and at the same time get that element: take a look at singleElement()
  • you have e.g. a list of invoices and want to get those invoice’s common invoice partner: take a look at extractSingleElementOrDefault()

Note: when using CollectionUtils frequently, it might make sense to statically import the class, i.e.

import static de.metas.util.collections.CollectionUtils.*;

#5

Annotate model interceptors and callouts with @Component

When you annotate a model interceptor with org.springframework.stereotype.Component, they will be initialized automatically on startup an you don’t need to hardcode them to be active elsewhere.

Also, you can have spring inject other components into them and don’t need to use Adempiere.getBean(ThatService.class) in the code. That way, it’s more transparent which services the interceptor or callout depends on.

Warning: make sure to give the componen a unique name.
Examples

// wrong; according to our naming pratice, there might be many `C_Flatrate_Term` classes
@Interceptor(I_C_Flatrate_Term.class)
@Component
public class C_Flatrate_Term
{
..
// correct; use the full classname as component name
@Interceptor(I_C_Flatrate_Term.class)
@Component("de.metas.contracts.interceptor.C_Flatrate_Term")
public class C_Flatrate_Term
{
..

Here is one example for a Component model interceptor: MKTG_ContactPerson.java .
And here is one for a Component callout: C_OrderLine.java

Note that the model interceptors are registered by the framework, whereas the callouts need to register themselves.


#6

Annotate ISingletonService implementors with @Service - if it makes sense

Like callouts and interceptors, you can also annotate classes that implement ISingletonService.
Service.get(ThatInterface.class) also looks into the spring context, so they will be returned.

The benefit is the same:

you can have spring inject other components into them and don’t need to use Adempiere.getBean(ThatService.class) in the code. That way, it’s more transparent which services the interceptor or callout depends on.

A note about unit testing:

  • If you don’t really need a spring context in your test, then keep things simple and don’t provide one.
  • If an ISingletonService that is also a @Service is needed during unit testing, you can create an instance manunally and register it with services.
    Example:
final UserRepository userRepository = new UserRepository();
final BPartnerBL bPartnerBL = new BPartnerBL(userRepository);
Service.registerService(IBPartnerBL.class, bPartnerBL);

Here, both UserRepository and BPartnerBL are spring components; BPartnerBL is also an implementor of ISingletonService.


#8

Use JUnit 5

Very recently, we added JUnit 5 to the parent pom.xml, so you can now use jupiter when writing tests.
The user guide can be found here https://junit.org/junit5/docs/current/user-guide/


#9

Use snapshot testing

Also very recently, we added https://github.com/json-snapshot/json-snapshot.github.io to the parent pom.xml,
so you can snapshot-test the objects created by your business logic.

Notes:

  • under the hood it’s using jackson to turn objects into snapshots
  • pls vote for our pull request :crossed_fingers: since the original version with string equality machting didn’t work for us. metasfresh is currently using this PR (jars on our nexus server) but would love to use the real thing instead.

#10

Take a look at the TrxListenerManager

See this this code from a model interceptor


	@ModelChange( //
			timings = { ModelValidator.TYPE_AFTER_NEW, ModelValidator.TYPE_AFTER_CHANGE }, //
			ifColumnsChanged = { I_C_OrderLine.COLUMNNAME_QtyOrdered,
					I_C_OrderLine.COLUMNNAME_M_Product_ID,
					I_C_OrderLine.COLUMNNAME_M_AttributeSetInstance_ID })
	public void vaildateQtyAvailableForSale(@NonNull final I_C_OrderLine orderLineRecord)
	{
		// has to contain everything that the method to be invoked after commit needs
		final CheckAvailableForSalesRequest checkAvailableForSalesRequest = createRequest(orderLineRecord);

		Services.get(ITrxManager.class)
				.getCurrentTrxListenerManagerOrAutoCommit()
				.newEventListener(TrxEventTiming.AFTER_COMMIT)
				.invokeMethodJustOnce(true)
				.registerHandlingMethod(trx -> checkAvailableForSales(checkAvailableForSalesRequest));
	}

	private Object checkAvailableForSales(@NonNull final CheckAvailableForSalesRequest checkAvailableForSalesRequest)
	{
		// TODO Auto-generated method stub
		return null;
	}

It will run checkAvailableForSales after the orderLineRecord was successfully changed and stored. checkAvailableForSales will not have any chance to prevent this.
also, if it’s fucking slow (which of course it shall not), it won’t keep the user waiting for the UI to respond to her e.g. change in the QtyOrdered field
but the method can add some warning flags and/or create a user notification. which will then be done/show pretty soon

Another common usage scenario for TrxListeners is when you want to fire an event…after some record was really crated or changed.
(background: another interceptor might be invoked after this one, and it might cause whole transaction to be rolled back alltogether)


#11

We just changed the recommendation for your local settings.xml file: See http://docs.metasfresh.org/developers_collection/en/getting_started_maven_settings.html