How to use Import Entry to distribute changes
Contents |
Introduction
This howto gives insight in how to write functionality which distribute changes using asynchronous events which are always executed at some point.
This type of functionality can for example be used to notify another system of a change without blocking the process which executes the change locally.
The approach shown here uses the Openbravo Import Entry framework. The import entry framework is used within OB for high volume data synchronization from WebPOS to the server.
This howto will guide you through the details on how to use the import entry framework for distributing changes in an asynchronous way. The approach works both in a single as well as a multi-server environment.
This howto assumes that the reader is familiar with the main concepts/information provided by the retail developers guide.
Example: distributing changes of customers
The example shown here has as goal to distribute changes done to customers from WebPOS to other systems.
First Step: Create Import Entry Record
The code below shows how to create a new import entry record within the customer loader hook. Openbravo provides many server side hooks which you can use. Alternatively you can use a business event handler to be create an import entry record in the same db transaction as the business documents.
import org.codehaus.jettison.json.JSONObject; import org.openbravo.base.provider.OBProvider; import org.openbravo.dal.core.OBContext; import org.openbravo.dal.service.OBDal; import org.openbravo.erpCommon.utility.SequenceIdData; import org.openbravo.model.ad.access.Role; import org.openbravo.model.common.businesspartner.BusinessPartner; import org.openbravo.retail.posterminal.CustomerLoaderHook; import org.openbravo.service.importprocess.ImportEntry; import org.openbravo.service.importprocess.ImportEntryManager; public class MyCustomerLoaderHook implements CustomerLoaderHook { @Inject private ImportEntryManager importEntryManager @Override public void exec(JSONObject jsonCustomer, BusinessPartner customer) throws Exception { String id = SequenceIdData.getUUID(); importEntryManager.createImportEntry(id, "MY_CUST_UPDATE", "{'customerId': '" + jsonCustomer.get("id") + "'}", false); ImportEntry importEntry = OBDal.getInstance().get(ImportEntry.class, id); // do any other things you want to do with the import entry record } }
Notes:
- the first parameter (the id) can be set to a random uuid (like the example). You can also it to set to a known id (for example in the case of the customer). If you do this then the system will automatically check for duplicates and ignore the duplicate import entry records.
- the type of data (MY_CUST_UPDATE in this example) is used to define the type of data. It must be a valid list reference value within the reference: 'Type of Import Data'
- the JsonInfo property contains the content you want to distribute.
- the last parameter (false) prevents the createImportEntry method from committing the transaction. Normally you should pass the value false.
- when the method returns you can retrieve the import entry and manipulate it if needed.
- if you don't want the import entry to be replicated to other servers then call this method on it with parameter true: ImportEntry.setObstsynCurrentServerOnly(true)
Instead of calling the ImportEntryManager.createImportEntry you can also directly create a new ImportEntry and save it. See the code within the ImportEntryManager.createImportEntry for details.
![]() | Starting from PR19Q2, Import entries can be created using a builder object such as follows (following the example above):
@Override public void exec(JSONObject jsonCustomer, BusinessPartner customer) throws Exception { ImportEntry importEntry = ImportEntryBuilder.newInstance("MY_CUST_UPDATE", "{'customerId': '" + jsonCustomer.get("id") + "'}") .create(); // do any other things you want to do with the import entry record } |
Second/Last Step: Create the Processor
The import entry record must be processed so it can notify the external system for the changes. For this purpose an import entry processor needs to be created.
import org.codehaus.jettison.json.JSONObject; import org.openbravo.base.weld.WeldUtils; import org.openbravo.dal.service.OBDal; import org.openbravo.model.common.businesspartner.BusinessPartner; import org.openbravo.service.importprocess.ImportEntry; import org.openbravo.service.importprocess.ImportEntryManager.ImportEntryQualifier; import org.openbravo.service.importprocess.ImportEntryProcessor; import com.openbravo.but.integration.rcu.connection.RCUConnection; @ImportEntryQualifier(entity = "MY_CUST_UPDATE") public class RCUCustomerImportEntryProcessor extends ImportEntryProcessor { protected ImportEntryProcessRunnable createImportEntryProcessRunnable() { return WeldUtils.getInstanceFromStaticBeanManager(RCUBusinessPartnerRunnable.class); } protected boolean canHandleImportEntry(ImportEntry importEntryInformation) { return "MY_CUST_UPDATE".equals(importEntryInformation.getTypeofdata()); } protected String getProcessSelectionKey(ImportEntry importEntry) { return importEntry.getOrganization().getId(); } private static class RCUBusinessPartnerRunnable extends ImportEntryProcessRunnable { protected void processEntry(ImportEntry importEntry) throws Exception { // call MyCust with updated customer, the importEntry has the json // in importEntry.getJsonInfo() JSONObject request = new JSONObject(importEntry.getJsonInfo()); if (request.has("customerId")) { MyCustConnection.createCustomer(request.getString("customerId"), false); } else { String id = request.getString("locationId"); BusinessPartner bp = OBDal.getInstance().get(BusinessPartner.class, id); MyCustConnection.createCustomer(bp); } // at the end set the ImportEntry as processed importEntry.setImportStatus("Processed"); OBDal.getInstance().commitAndClose(); } } }
Notes:
- MyCustConnection is a custom class which sends the customer to the other system, in your case this would be another class.
- the type of import data (in this example: MY_CUST_UPDATE) is used in both the annotation in the top of the class as well as in the canHandleImportEntry method to validate if an import entry record should be processed by this class.
- the processing logic is implemented within an inner class. The inner class is returned by the createImportEntryProcessRunnable method.
- the getProcessSelectionKey method is used to support parallel processing of import entries. If import entries should be processed sequentially then these should have the same value returned here. For example, by returning the organization id the following processing order is used:
- all import entries within one organization/store are processed sequentially (in order of their creation)
- import entries of different organizations/stores can be/are processed parallel.
- At the end set the import entry to processed and commitAndClose.
Processing
After implementing the import entry processor there is nothing else to be done. The import entry manager will automatically pick up any new import entries and call the processor automatically.
Advantages of using the OB Import Entry Framework
Using the Import Entry framework for this logic has some benefits/advantages:
- The import entry record is created in the same database transaction as your other changes. So there is transactional integrity.
- The import entry record is guaranteed to be processed at some point.
- If the import entry record processing fails it is automatically retried until it succeeds.
- The import entry record is archived automatically. This supplies you with a processing log which shows what (json and type of import entry) is processed at what time.