Integrating Liferay Commerce using an ETL (Part 4 and Final: Data Processing and Product Loading)

DISCLAIMER:  This blog  post has  Spanish and English Version.

Finished the step of Part 3: Liferay Components for Talend Open Studio

We have reached the last blog in this series and the culmination of all our efforts: where we get to run our “Job” which will perform the product loading in to Liferay Commerce in a matter of seconds.

In order to achieve our grand finale we only have to learn how to create routines, which is nothing more than a Java class with methods that will help us in the data processing and mapping between the input fields and the output fields.

1. Continuing where we left off in the previous Blog, we start by creating a routine which will be able to transform the data that we send to the Liferay component according to the OpenAPI specification.

  • In Repository, go to “Code” > “Routines” and right click and choose the option “Create Routine” (for more information about how to create a “User Routine”, check this documentation)

  • Give a name to the routine. In my case, I chose “TransformToLiferay”. It will create a .java file. You can download my routine from my GitHub repository.
  • In the Java file, we will have already 5 methods created for us. We can call these methods from the “tMap” component to transform the data and pass then to the “tLiferayOutput” component.
Note: If you are wondering when will it be necessary to include a method in the routine and when it isn’t, take the following examples; Firstly every time in the API you call the input parameter as a "primitive type" or "plain text" variable, you will not need to go through any transformation. It can be mapped directly, see the example in the following image:

Secondly (where we do need to use it) every time when the API that you call’s  input parameter is a “reference type” variable, you will need to go through a transformation. Talend already includes many serial routines. For more information about the available "system" routines, check the Talend documentation.                                                         Important: In the Liferay API, many fields that appears to be plain text, are in fact not. Actually, they are multi-language fields, like many other fields in Liferay Portal. To treat those, you have the method “ setLocalizedField” available in the routine.

2. Before going back to product mapping, it is important to highlight again that all Liferay Headless Commerce APIs are documented in Swagger Hub, where you can check which fields are included and which are mandatory for every API and method:

  • All Lifery Commerce APIs begin with the “headless-” prefix. So, in Swaggerhub, the URL will be like the following:  “https://app.swaggerhub.com/apis/liferayinc/commerce-admin-catalog/v1.0”
  • To build the endpoint, remove from the URL “https://app.swaggerhub.com/apis/liferayinc/” and change the URL until “v1.0”. It will end up like: commerce-admin-catalog/v1.0. Then, include in the beginning “headless-” and we include the Commerce API: /headless-commerce-admin-catalog/v1.0”.
Note - In case you want to make API calls using “curl” or “Postman”, you only need to add your Liferay domain and port (like “localhost:8080”) and the path “/o/”, which it’s the exposed OSGi path. An example for the GET method in Products: http://localhost:8080/o/headless-commerce-admin-catalog/v1.0/products. For more information about the Liferay Headless APIs, check the documentation.

3. Now that you know where to find all the information and documentation, we will begin mapping the fields, using the routine contexts and methods. Double-click on “tMap”.

  • In the “tLiferayRow” column, fill-in the 3 mandatory fields (those we don’t have in the database), with the “contexts” created before: 

  • We will start with the “description” field. In the “tLiferayRow” column, go to the “description” field and click on the “Expression” column on the left, as indicated in the image below:

  • Now in “Categories” click on “User defined” > double-click on “setLocalizedField” and pass to the method the required parameters, the “context.LANGUAGE_PT_BR” context and the variable “row1.description”, retrieved from the database. It should look like the image below:

  • Repeat the same process for “name” and “shortDescription” fields.
  • The process is the same for the “image” field, only we will use a different method. In this case, we choose the “setImage” method. Check the image below:

Note: I highlighted in yellow the call to “getLocalizedField” inside the “setImage” method.This is necessary because the “title” expects a JSON with the language translations. 
Important: change the image path to the folder where you downloaded the images you downloaded at the beginning of these blog posts.
  • The last field we will map will be the “skus” and we will use the “setSKU” method. It should look like the image below:

Note: If you pay attention, the “skus” field is an array of SKU objects. This is because a product can; and usually  will have more than one SKU.

By the way, what’s a SKU (Stock Keeping Unit)?

They are alphanumeric codes used to identify a product and its variations.. 

The SKU is extremely important for digital commerce and is nothing more than the necessary identifier for a given product and thus have better control of your stock, leaving the products separated according to their characteristics. 

SKUs should not be confused with UPCs (Universal Product Code). SKUs are created by individual suppliers and can form any alphanumeric convention. Often suppliers try and follow a pattern that may be somewhat related to the product. But the same product could be referred to by different suppliers or wholesalers by their own individual SKU codes. A UPC is the numeric representation of a product barcode (EAN). These codes are registered for particular products and will be the same regardless of the supplier or wholesaler.  

For example, a white shirt that has four different sizes (S, M, L, XL). We can make distinctions between them using SKUs. For example: White Shirt S will have SKU: CB1, White Shirt XL will have SKU: CB4.

  • Lastly, we will map a very important field: “externalReferenceCode”. This field must always be mapped with a unique code that is the reference between the product within Liferay Commerce and the product in the External System. Although it is not a mandatory field, it is important to utilise it to prevent the generation of duplicates when syncing with external systems as this the primary identifier used by the external system. 

4. Now that mappings are finished, the “tMap” will look like:

  • Click on “Apply” and “Ok”.

5. Before running the Job, we will change the connection settings in “Advanced Settings” in “tLiferayOutput” component:

Note: if you don’t change them, you will receive a “NullPointer” because it will not remember the previous data. This is a workaround for a known issue. If your connection between Liferay and the database is working well and you see that “NullPointer” error in “connection”, it will be this problem in Talend. In these cases, change the value in “Rest Connection Timeout (s)”  and save.

6. Finally, we go to the “Run” tab and we run the Job.

  • The outcome should look like the image below, with 1 row per product:

  • Now you can access Liferay Commerce and check that the products are correctly loaded. In the image below you can see the 10 products we’ve successfully loaded: 

  • Clicking on the first product we can see the information in detail that we’ve just imported:

  • Checking the SKUs, we can check that everything went well: 

                                                                          …. Commercial information ...

                                                                       … price and promotional price …. 

  • You can see product on the Website you assigned to the “Channel”, like in the image below:

  • If you want to try to do the whole product load (without the limit), change the query to “"SELECT * FROM products” (without the limit), click on “Guess schema”, save and run again the job. You will see 64 products registered:

Case you need to debugger the commerce code in Liferay, you can find the code source for 2.0.7 version is tag here:  https://github.com/liferay/liferay-portal/tree/commerce-2.0.7

 

Final closing conclusion

We have seen how to register a Product Catalog in Liferay Commerce with only 3 components and 5 methods.

As we saw in the first blog post of this series, we used a database as the data source, but you might use any data source and external system. Having an integration like the one we have seen for the product catalog makes a task that could be arduous (keeping the update between two systems) easy, fast and practical.

That ETL we created can be exported and be attached to a cron job in your server or if you use Liferay DXP, you can include it in “Data Integration Admin”, so you can manage Jobs inside the Control Panel:

PS: Thanks to Bernard Mc Closkey (Commerce Solution Architect for EMEA) for helping me on the translation of this Blog post.

Blogs

Hi Roselaine,

 

I am trying to follow step by step this blog, however i am getting some issues when i run the job in talend. Something related with unexpected character: (the job is called ProductExporterJob

 

Doing a debug in talend, i can catch the message that is going to send to liferay output: ------------------------------------

{"active": true, "attachments": null, "catalogId": 36306, "categories": null, "configuration_allowBackOrder": null, "configuration_allowedOrderQuantities": null, "configuration_displayAvailability": null, "configuration_displayStockQuantity": null, "configuration_inventoryEngine": null, "configuration_lowStockAction": null, "configuration_maxOrderQuantity": null, "configuration_minOrderQuantity": null, "configuration_minStockQuantity": null, "configuration_multipleOrderQuantity": null, "createDate": null, "defaultSku": null, "description": "{\"pt_BR\":\"Minian e um anticoncepcional, ou seja, e usado para impedir que voce fique gravida. Quando usado corretamente, (sem esquecimento), a possibilidade de engravidar e muito baixa. MINIAN E UM MEDICAMENTO. SEU USO PODE TRAZER RISCOS. PROCURE UM MEDICO OU UM FARMACEUTICO. LEIA A BULA. \"}", "displayDate": null, "expando": null, "expirationDate": null, "externalReferenceCode": null, "id": null, "images": "[{\"attachment\":\"ATTACHMENT\",\"title\":{\"pt_BR\":\"Minian\"},\"externalReferenceCode\":\"10880138915\",\"neverExpire\":true}]", "metaDescription": null, "metaKeyword": null, "metaTitle": null, "modifiedDate": null, "name": "Minian", "neverExpire": null, "options": null, "productId": null, "productSpecifications": null, "productType": "simple", "relatedProducts": null, "shippingConfiguration_depth": null, "shippingConfiguration_freeShipping": null, "shippingConfiguration_height": null, "shippingConfiguration_shippable": null, "shippingConfiguration_shippingExtraPrice": null, "shippingConfiguration_shippingSeparately": null, "shippingConfiguration_weight": null, "shippingConfiguration_width": null, "shortDescription": "{\"pt_BR\":\"Minian\"}", "skus": "[{\"promoPrice\":25.59,\"manufacturerPartNumber\":\"88102\",\"gtin\":\"10880138915\",\"purchasable\":true,\"price\":32.16,\"sku\":\"10880138915\",\"externalReferenceCode\":\"1003300980019\"}]", "subscriptionConfiguration_enable": null, "subscriptionConfiguration_length": null, "subscriptionConfiguration_numberOfLength": null, "subscriptionConfiguration_subscriptionType": null, "subscriptionConfiguration_subscriptionTypeSettings": null, "tags": null, "taxConfiguration_id": null, "taxConfiguration_taxCategory": null, "taxConfiguration_taxable": null, "urls": null}

------------------------------------  

And here is the exception thrown:

 

Exception in component tLiferayOutput_1 (ProductExporterJob) java.io.IOException: javax.json.stream.JsonParsingException: Unexpected char 77 at (line no=1, column no=1, offset=0)     at com.liferay.talend.avro.RejectHandler.reject(RejectHandler.java:60)     at com.liferay.talend.runtime.writer.LiferayWriter.write(LiferayWriter.java:188)     at commerce_products_poc.productexporterjob_0_1.ProductExporterJob.tDBInput_1Process(ProductExporterJob.java:3511)     at commerce_products_poc.productexporterjob_0_1.ProductExporterJob.runJobInTOS(ProductExporterJob.java:4709)     at commerce_products_poc.productexporterjob_0_1.ProductExporterJob.main(ProductExporterJob.java:4531) Caused by: javax.json.stream.JsonParsingException: Unexpected char 77 at (line no=1, column no=1, offset=0)     at org.glassfish.json.JsonTokenizer.unexpectedChar(JsonTokenizer.java:601)     at org.glassfish.json.JsonTokenizer.nextToken(JsonTokenizer.java:418)     at org.glassfish.json.JsonParserImpl$NoneContext.getNextEvent(JsonParserImpl.java:426)     at org.glassfish.json.JsonParserImpl.next(JsonParserImpl.java:376)     at org.glassfish.json.JsonReaderImpl.readValue(JsonReaderImpl.java:145)     at com.liferay.talend.avro.IndexedRecordJsonObjectConverter.toJsonObject(IndexedRecordJsonObjectConverter.java:165)     at com.liferay.talend.runtime.writer.LiferayWriter.doInsert(LiferayWriter.java:107)     at com.liferay.talend.runtime.writer.LiferayWriter.write(LiferayWriter.java:173)     ... 3 more

 

 

Thanks in advance for your response.

Hi David, 

 

The problem is on field "name" that do you are sending like a simple string without pass for the method "setLocalizedField".

 

To correct it, do the same process that you have on "shortDescription".

Regards.

Hi Roselaine,

Thank you for a very informative article. After a few days, I am able to follow all of your articles and got the demonstration working.

However, following the 4th articles there is an error I think everybody should be aware of:

I do not know if everybody has this issue but there are 2 parameters from the Liferay columns that have similar NAME: catalogId and catalog_id.

the context.CATALOG_ID coming from Talend context parameters should be mapped with Liferay's catalogId. If you map context.CATALOG_ID with catalog_id you will be getting an error:

java.lang.NullPointerException.