View source | Discuss this page | Page history | Printable version   
Toolbox
Main Page
Upload file
What links here
Recent changes
Help

PDF Books
Add page
Show collection (0 pages)
Collections help

Search

How to implement Multi-Server Process Calls

Contents


Bulbgraph.png   Starting from RR16Q3 below functionality is available

Introduction

The OB Commerce infrastructure supports multi-server handling of requests from WebPOS to Store and Central server. Multi-server handling means that requests are send to one server and automatically replicated to other servers. Also WebPOS will transparently switch from one server to another if a server goes offline.

The logic to replicate transactions from one server to another means that logic is sometimes run directly as part of the request from WebPOS and in other cases is run through the import entry approach.

This HowTo describes the steps to take to implement a transaction service which can run transparently in a multi-server environment.

Note: if you need to implement a web-service which processes a request locally in the store when offline and calls central when online, then it is better to check out this howto which provides a simpler approach on implementing this type of behavior.

Installing the how to module

The examples provided in this howto can be found in the Store Server Howto module. The howto module can be downloaded using the hg clone command in the modules directory:

 hg clone https://code.openbravo.com/erp/mods/org.openbravo.retail.storeserver.howto

After this execute the 'ant smartbuild -Dlocal=no' command in the main project directory.

Other reading

To understand the concepts described in this howto it is useful to read the following documentation:

Implementing a Multi-Server Process Call

To implement a multi-server process call the following artifacts need to be developed:

To make the best use of this HowTo it is strongly advised to install/run a multi-server development environment.

This howto takes you through all of the above steps to get to a fully implemented process call.

Result

To start with the intended result of the howto. The idea is to add a button to the ticket line properties which (when clicked) sends a request to the Store/Central server. This button is shown when clicking on a line in the ticket. See the 'Check Multi Server' button in the screenshot.


HowTo-Multi-Server-Concept-Call-Result-Button.png


When this button is pressed a request is send to either the store or central server (depending which one is available). A confirmation message is shown with details on the routing of the request.


HowTo-Multi-Server-Concept-Call-Result-Msg.png


Implementing Server Side Class

A first step is to implement the class which executed your intended functionality on the server.

To be multi-server process enabled the only thing you need to do is inherit from MultiServerJSONProcess and implement two methods.

public class CheckMultiServer extends MultiServerJSONProcess {
 
  @Override
  public JSONObject execute(JSONObject jsonRecord) {
// your functional code, returning a JSONObject
  }
 
  @Override
  protected String getImportEntryDataType() {
// return your import entry data type
  }
 
}

The first method (execute) should implement your business logic. Note:

To support the import entry processing approach some additional code is needed, nl. an ImportEntryProcessor.

In the sample code this is done as follows:

 
 @ImportEntryQualifier(entity = "OBSTHOW_CheckMultiServer")
  @ApplicationScoped
  public static class MultiServerJsonHowToImportEntryProcessor extends ImportEntryProcessor {
 
    protected ImportEntryProcessRunnable createImportEntryProcessRunnable() {
      return WeldUtils.getInstanceFromStaticBeanManager(SynchronizedProcessRunnable.class);
    }
 
    protected boolean canHandleImportEntry(ImportEntry importEntryInformation) {
      return "OBSTHOW_CheckMultiServer".equals(importEntryInformation.getTypeofdata());
    }
 
    protected String getProcessSelectionKey(ImportEntry importEntry) {
      return (String) DalUtil.getId(importEntry.getOrganization());
    }
  }
 
  private static class SynchronizedProcessRunnable extends MobileImportEntryProcessorRunnable {
 
    @Override
    protected Class<? extends JSONProcessSimple> getJSONProcessorClass() {
      return CheckMultiServer.class;
    }
 
    protected void processEntry(ImportEntry importEntry) throws Exception {
 
      // do some custom checks relevant for your case, for example
      // if the entry can be processed now or should wait
 
      // do the real processing
      super.processEntry(importEntry);
    }
 
  }
}

Note:

The full code of the example class can be found here.

Service Definition

The next step is to define the class as a service in the system. This is done through the Mobile Services window. The main thing to note is that the multi-server-call service needs to be set as a Fail-Over service.


HowTo-Multi-Server-Concept-Call-Mobile-Service.png


Import Entry Data Type Reference

The service class is also called as part of the import entry processing framework. Therefore we also need to define the import entry data type as a list-reference record in the 'Type of Import Data' list reference.

Note: the search key in the list reference record must be exactly the same as set in this record (in our example 'OBSTHOW_CheckMultiServer').


HowTo-Multi-Server-Concept-Call-Mobile-Import-Entry-DataType.png

Client Side Implementation

The client side code runs in WebPOS and calls the service. In our example we added a button in the line details (shown when clicking on a line in the ticket). The full javascript sample code can be found here.

Most of the javascript code is related to adding the button.

One specific thing to note is that application exceptions thrown on the server are returned as json to the client. In this error case the success callback will still be called and your application code needs to detect that an error occurred. An example on how to do this is shown below.

(function () {
 
  enyo.kind({
    kind: 'OB.UI.SmallButton',
    name: 'OB.OBPOSPointOfSale.UI.EditLine.CheckMultiServer',
    content: 'Check Multi Server',
    classes: 'btnlink-orange',
    tap: function () {
      var process = new OB.DS.Process('org.openbravo.retail.storeserver.howto.CheckMultiServer');
      process.exec({
 
        // it is best to set a messageId from the client so when communicating between servers
        // duplicates are detected
        messageId: OB.UTIL.get_UUID(),
 
        // the message content must be in the data property, data may also be an array
        productId: this.owner.owner.line.get('product').get('id')
      }, function (data) {
        if (data.exception) {
          OB.UTIL.showError('Error occurred ' + data.exception.message);
        } else {
          OB.UTIL.showConfirmation.display('Multi Server Call Done', 'Call to server result: product info: ' + data.productInfo + ' with message ' + data.msg, [{
            label: OB.I18N.getLabel('OBMOBC_LblOk'),
            isConfirmButton: true
          }], {
            autoDismiss: false,
          });
        }
      });
    }
  });
 
  //Register the button...
  OB.OBPOSPointOfSale.UI.EditLine.prototype.actionButtons.unshift({
    kind: 'OB.OBPOSPointOfSale.UI.EditLine.CheckMultiServer',
    name: 'checkMultiServer'
  });
 
}());

The main logic is implemented in the tap handling:

Response JSON Definition

Success Response

The json below shows an example of a success response for this howto.

Note that only the data property value (a json object) is passed to the success callback. So your client side application code will only see that part.

{
    "response": {
        "data": {
            "processTime": "Mon May 09 07:15:42 CEST 2016",
            "msg": "This is a store server (VALLBLANCA)  from source WEBPOS with messageId C1991213FB7F5F59BD3D0FA0D650D75D processed on Mon May 09 07:15:42 CEST 2016. From WebPOS to Store",
            "productInfo": "Product WVG\/S0015"
        },
        "messageId": "C1991213FB7F5F59BD3D0FA0D650D75D",
        "status": 0,
        "contextInfo": {
            "userId": "3073EDF96A3C42CC86C7069E379522D2",
            "roleId": "5FA11B3DD8F04C0986C774624809C31E",
            "orgId": "D270A5AC50874F8BA67A88EE977F8E3B",
            "clientId": "39363B0921BB4293B48383844325E84C"
        }
    }
}

Request with result property

A special json is created when a request is processed on 2 servers sequentially. The result of the first server is also send to the other server. This is accomplished by including the result of the first server in the json which was originally send in. The result is placed in the '_result' property of the json. In this way the second/other servers can take into account that the json was already processed and use the results of the first server.

 
{
    "messageId": "90F4928154763F8CBD67134141EB05B2",
    "_source": "CENTRAL",
    "posTerminal": "9104513C2D0741D4850AE8493998A7C8",
    "data": [{
        "messageId": "A48125A5F66AB73A0B61569534B5AD22",
        "data": [{
            "hasbeenpaid": "N",
            "isbeingprocessed": "N",
...
        "appName": "WebPOS",
        "_serviceName": "org.openbravo.retail.posterminal.ProcessCashClose"
    }],
    "client": "39363B0921BB4293B48383844325E84C",
    "organization": "D270A5AC50874F8BA67A88EE977F8E3B",
    "pos": "9104513C2D0741D4850AE8493998A7C8",
    "terminalName": "VBS-1",
    "appName": "WebPOS",
    "_result": {
        "data": {
            "result": [{
                "_serviceName": "org.openbravo.retail.posterminal.OrderLoader",
                    "orgId": "D270A5AC50874F8BA67A88EE977F8E3B",
                    "clientId": "39363B0921BB4293B48383844325E84C"
...
                }
            }],
            "status": 0
        },
        "messageId": "90F4928154763F8CBD67134141EB05B2",
        "posTerminal": "9104513C2D0741D4850AE8493998A7C8",
        "status": 0,
        "contextInfo": {
            "userId": "3073EDF96A3C42CC86C7069E379522D2",
            "roleId": "5FA11B3DD8F04C0986C774624809C31E",
            "orgId": "D270A5AC50874F8BA67A88EE977F8E3B",
            "clientId": "39363B0921BB4293B48383844325E84C"
        }
    }
}

Error Response

A handled error response will be something similar to the json shown below. The callback gets this json.

{
    "exception": {
        "message": "JSONObject[\"_source\"] not found.",
        "status": {
            "status": -1,
            "error": {
                "message": "JSONObject[\"_source\"] not found.",
                "title": "",
                "className": "org.codehaus.jettison.json.JSONException"
            },
            "totalRows": 0,
            "contextInfo": {
                "userId": "3073EDF96A3C42CC86C7069E379522D2",
                "roleId": "5FA11B3DD8F04C0986C774624809C31E",
                "orgId": "D270A5AC50874F8BA67A88EE977F8E3B",
                "clientId": "39363B0921BB4293B48383844325E84C"
            }
        }
    }
}

Relevant json properties

There are several json properties which are relevant for when developing server side processing logic. All properties are located in the root json object of the root json object:

Example when a request is send in like this to the store server:

{
    "messageId": "90F4928154763F8CBD67134141EB05B2",
    "_source": "WEBPOS",
    "posTerminal": "9104513C2D0741D4850AE8493998A7C8",
    "_tryCentralFromStore": true,
    "data": {
        "productId": "9104513C2D0741D4850AE8493998A7C8"
    }
}

and it is processed on central first then the store server will receive a json to process:

{
    "messageId": "90F4928154763F8CBD67134141EB05B2",
    "_source": "CENTRAL",
    "posTerminal": "9104513C2D0741D4850AE8493998A7C8",
    "data": {
        "productId": "9104513C2D0741D4850AE8493998A7C8"
    },
    "_result": {
        "inventory": 10.5
    }
}

The main thing to note:

Retrieved from "http://wiki.openbravo.com/wiki/How_to_implement_Multi-Server_Process_Calls"

This page has been accessed 303 times. This page was last modified on 26 May 2016, at 16:14. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.