Adapter code examples

Adapters concept

The principle of an import adapter is to provide a dedicated conversion processing for any incoming data format (text-based) into standard Simplicit&eacute® XML format.

The principle is to read data from an input stream (depending on the adapter engine, see below) and to write results to an output stream (depending on the adapter engine, see below).

Scripted adapter

It is possible to write scripted Java adapters. Such a script can be uploaded as a document field or can be directly created and edited from the generic web UI using the "Edit script" action button)

To configure a scripted Java adapter, the processing type has to be set to "JAVA" and the script document field must not be empty.

The this variable is set to the current instance of com.simplicite.util.integration.SimpleScriptedAdapter before calling the adapter script:

The following additional packages are included by default (see this document for the list of packages included by default in all scripts)

java.io
com.simplicite.util.integration

Other custom includes can be added when needed (e.g. POI or apache commons libs).

Example: Simple CSV adapter example (named myAdapter) for loading system parameters using integration APIs (input format is <system parameter name>;<system parameter value>[;<module name>], module is not mandatory in case of update).

MyAdapter.process = function() {
    console.log("Processing start");
    var out = this.getOutputWriter();
    var log = this.getLogWriter();
    var n = 1;
    var l;
    while (l = this.getInputReader().readLine()) {
        console.log("Line " + n + " = [" + l + "]");
        var v = l.trim().split(";");
        if (l.length() > 0  && !l.startsWith("#")) {        
            var o = new ObjectXML("SystemParam", "upsert");
            var d = new DataXML();
            d.add("sys_code", v[0]);
            d.add("sys_value", v[1], ObjectField.TYPE_LONG_STRING);
            if (v.length == 3)
                d.add("row_module_id.mdl_name", v[2]);
            o.addData(d);
            out.println("<!-- " + l + " -->");
            out.println(o.toString());
            log.println("Line " + n + " processed: " + l);
        } else
            log.println("Line " + n + " ignored: " + l);
        n++;
    }
    console.log("Processing end");
};

Using this adapter the input data:

# Test parameter
TEST;test;Test

Produces this XML output (that is processed):

<?xml version="1.0" encoding="UTF-8"?>
<simplicite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.simplicite.fr/base" xsi:schemaLocation="http://www.simplicite.fr/base http://www.simplicite.fr/schemas/base.xsd">
<object>
    <name>SystemParam</name>
    <action>upsert</action>
    <data>
        <sys_code>TEST</sys_code>
        <sys_value><![CDATA[test]]></sys_value>
        <row_module_id.mdl_name>Test</row_module_id.mdl_name>
    </data>
</object>
</simplicite>

And the following log:

2014-09-09 18:11:55,157 INFO  [/demo] Start adapter: SimpleScriptedAdapter
2014-09-09 18:11:55,158 INFO  [/demo]   User: designer
2014-09-09 18:11:55,158 INFO  [/demo]   Date: Tue Sep 09 18:11:55 CEST 2014
Line 3 processed: TEST;test;Tutorial
2014-09-09 18:11:55,191 INFO  [/demo] End adapter: SimpleScriptedAdapter
2014-09-09 18:11:55,191 INFO  [/demo]   Date: Tue Sep 09 18:11:55 CEST 2014
2014-09-09 18:11:55,192 INFO  [/demo]   Status: T
2014-09-09 18:11:55,214 INFO  [/demo] Start import:
2014-09-09 18:11:55,214 INFO  [/demo]   User: designer
2014-09-09 18:11:55,214 INFO  [/demo]   XMLSupervisor: 6
2014-09-09 18:11:55,218 INFO  [/demo] AUTO COMMIT
2014-09-09 18:11:55,218 INFO  [/demo] Start import object SystemParam:
2014-09-09 18:11:55,218 INFO  [/demo]   Found field sys_code = [TEST]
2014-09-09 18:11:55,218 INFO  [/demo]   Found field sys_value = [test]
2014-09-09 18:11:55,218 INFO  [/demo]   Found field row_module_id.mdl_name = [Tutorial]
2014-09-09 18:11:55,219 INFO  [/demo]   Found internal key row_id = 184
2014-09-09 18:11:55,220 INFO  [/demo]   Action: UPDATE
2014-09-09 18:11:55,221 INFO  [/demo]   Save ok.
2014-09-09 18:11:55,222 INFO  [/demo] Close import:
2014-09-09 18:11:55,222 INFO  [/demo]   Status: OK
2014-09-09 18:11:55,222 INFO  [/demo]   Time(sec): 0.008

Note: instead of generating an XML output it is also possible to call directly business object methods within the adapter. See below.

Call directly business object methods within the adapter

Instead of generating XML an adapter can directly use the business object methods to import records. In such a case the XML import itself is useless and can be inhibited (see. example bellow)

MyAdapter.process = function() {
    var n = 1;
    var line;
    var c = new CSVTool(';', '"', "%CR%");
    var obj = this.getGrant().getTmpObject("MyObject");
    this.getInputReader().readLine(); // Read Header line

    while ((line = this.getInputReader().readLine()) !== null) {
        this.getLogWriter().println("Line " + (n++) + " started");
        var vals = c.parse(line,true);
        var field1 = vals[0];
        var field2 = vals[1];
        var field3 = vals[2];

        obj.resetFilters();
        obj.getField("field1").setFilter(field1);
        if (obj.count() == 0) {
            obj.resetValues(true); // Prepare record for creation (true is to evaluate default values)
            obj.setFieldValue("field2", field2);
            obj.getFieldValue("field3", field3);
            new BusinessObjectTool(obj).validateAndSave();
        } else {
            this.getErrorWriter().println("ERROR line " + n + " : object already exists");
            this.setStatus('E');
        }
        this.getLogWriter().println("Line " + n + " finish");
    }
};

MyAdapter.postProcess = function() {
    // Inhibitate output to avoid useless XML import
    this.setOutputStream(null); 
};

Java adapter

To have higher performance adapters it may be sometimes preferable to use compiled Java adapters instead of scripted Java adapters.

To configure a compiled Java adapter, the processing type has to be set to "JAVA" and the script document field must be empty.

By default, the fully qualified class name of the class to implement is com.simplicite.adapters.<Module name>.<Adapter name>.

If you specify an explicit class name it can be either a simple name (in that case the fully qualified class name of the class to implement is com.simplicite.adapters.<Module name>.<Explicit simple class name>) or a fully qualified module and class name.

Anyway, this class must implement the com.simplicite.utils.integration.AdapterInterface interface, which means implementing the two following methods:

public void init(Grant g);
public void process(InputStream in, OutputStream out);

In order to simplify adapter development several pre-implemented adapters are provided (text file adapter, XML or JSON parsers adapters, ...).

A line based adapter is provided as an abstract class to be overridden com.simplicite.util.integration.LineBasedAdapter. The methods to implement are in this case (current grant is available using the getGrant() method).:

public String preProcess(}
public String postProcess()
public String processLine(int lineNumber, String line)

A SAX XML parser based adapter is provided as an abstract class to be overridden com.simplicite.util.integration. SAXParserAdapter. You just need to implement the SAX handler like. Typical usage is as follows:

private class MyAdapter extends com.simplicite.util.integration.SAXParserAdapter {
    private class MyHandler extends com.simplicite.util.integration.SAXParserHandler {
        public MyHandler(OutputStream out, OutputStream err, OutputStream log) { super(out, err, log); }
            // You SAX handler implementation here...
        }

        public void process() throws InterruptedException {
            setParser(this.new MyHandler(getOutputStream(), getErrorStream(), getLogStream()));
            super.process();
        }
    }
}

And a simplified wrapper adapter for SAX parser based adapter is also provided as an abstract class to be overridden: com.simplicite.util.integration. SimpleSAXParserAdapter. The methods to implement are in this case (current grant is available using the getGrant() method).:

public void startProcess()
public void startTagProcess(String uri, String localName, String qName, Attributes attributes)
public void processValue(String value)
public void endTagProcess(String uri, String localName, String qName)
public void endProcess()

AWK adapter

Although it can now be considered as deprecated, it is also possible to write adapters in the well-known UNIX AWK scripting language.

The advantage of using this scripting language is that it is highly efficient for simple text transformations. The main drawback is that it does not allow to use Simplicit&eacute,® Java APIs neither to access to current user grant data.

To configure a AWK adapter, the processing type has to be set to "AWK" and the script content must contain the AWK script itself.

Input and output streams are implicit with AWK.

Example: Simple CSV adapter example for system parameters (input format is <system parameter name>;<system parameter value>[;<module name>], module is not mandatory in case of update).

BEGIN { FS=";" }
/^[ \t]*#/ { next }
{
print "<object>"
print   "<name>SystemParam</name>"
print   "<action>upsert</action>"
print   "<data>"
print       "<sys_code>"$1"</sys_code>"
print       "<sys_value>"$2"</sys_value>"
if ($3 != "")
    print       "<row_module_id.mdl_name>"$3"</row_module_id.mdl_name>"
print   "</data>"
print "</object>"
}

Standard adapters

Several standard adapters are provided by default:

Custom action to submit object data to adapter

You can configure a business object custom action that submits some of your text field content to an adapter.

Example:

MyObject.myAction = function() {
    var data = this.getFieldValue("myTextField");
    var res = Integration().importADP(this.getGrant(), "MyAdapter", data, this.getName(), null);
    return "<pre>" + res.getAdapterLog() + "\n" + res.getResultLog() + "</pre>"
};

For a document field the first line would be:

    var data = new java.lang.String(this.getField("myDocumentField").getDocument().getBytes(true));

Custom page to submit data to adapter

You can configure a simple custom page (external object) that displays an upload form for submitting a file to an adapter:

Example:

MyExternalObject.form = function(params) {
    var form = "uploadform";
    var tab = 1;
    var h = new HTMLTool.openSimpleMultipartForm(form, params.getLocation());
    h += "<table class=\"workform\" style=\"width: 100%;\">";
    h += "<tr><td class=\"workfieldname\"><div class=\"workfieldname\">" + this.getGrant().T("UPLOAD") + "</div></td>";
    h += "<td class=\"workfield\"><div class=\"workfield\">";
    h += HTMLTool.fileInput(form, "file", 80, tab++) + "&nbsp;";
    h += HTMLTool.submit(form, "ok", this.getGrant().T("OK"), null, "buttonaction", tab++);
    h += "</div></td></tr></table>";
    h += HTMLTool.closeForm(form, "file", "ok");
    return h;
};

MyExternalObject.display = function(params) {
    var g = this.getGrant();
    if (params.getMethod() == "GET") {
        return MyExternalObject.form.call(this, params);
    } else {
        var m = "";
        try {
            var file = params.getDocument("file");
            var data = new java.lang.String(file.data);
            if (Tool.isEmpty(data)) throw new Exception("No data");
            var res = Integration().importADP(g, "MyAdapter", data, file.path, null);
            m += "<div class=\"workinfo\">Your file has been uploaded!</div>";
            m += "<pre class=\"mono\" style=\"max-height: 300px; overflow: auto;\">" + res.getAdapterLog() + "\n" + res.getResultLog() + "</pre>"
        } catch(e) {
            m += "<div class=\"workerror\">" + e.message + "</div>"
        }
        return m + MyExternalObject.form.call(this, params);
    }
};

Intead of a file upload input, you can easily transpose this example to use a textarea for data.

Note: this custom page offers a subset of the features available out of the box in the more complex XML import page of the generic UI.