Beispielmodul

Funktionalität

Unser Modul soll Daten aus Datenbanktabellen mit Hilfe einer SQL-Abfrage selektieren und als kommaseparierte Liste in eine Datei schreiben. Das Modul bekommt den Namen DsModDemo. Es erwartet eine oder mehrere Quelltabellen und schreibt die von der SQL-Abfrage gelieferten Daten in eine Datei ("Flat File").

Realisierung

Zur Vereinfachung leiten wir unsere Modulklasse von einer Basisklasse ab, in der einige Komfortmethoden bereits implementiert sind.

public class DsModDemo extends BaseModule implements datasqillProgram {

Darin implementieren wir die Methode "execute":

public datasqillTransformationAnswer execute(datasqillTransformationRequest request) {
    ...
}

und führen dort folgende Schritte aus:

  1. Es wird der Start-Zeitpunkt bestimmt und eine leere Response erzeugt.
  2. Die Parameter aus dem eingehenden Request werden gelesen.
  3. Die Datenbankverbindung für die Quelltabellen wird geöffnet.
  4. Abhängig vom Kommando wird die Transformation validiert oder ausgeführt.
  5. Abschließend werden die Resultate (Metadaten) der Ausführung in die Antwort geschrieben.

Bestimmung des Start-Zeitpunkts und Erzeugen einer leeren Antwort:

// start time measuring and create response
long startTime = System.nanoTime();
datasqillTransformationAnswer response = new datasqillTransformationAnswer(request.getRequestId(), 0, "DsModDemo", null, 0, null, -1, null, null);

Lesen der Parameter aus dem eingehenden Request:

// read parameters
Action action = getAction(request);
String sqlQuery = getActionString(action);
String cmd = getCommand(request);
ObjectDs target = getTarget(action);
List<ObjectDs> sources = getSources(action);
Long targetDatabaseID = getDatabaseId(sources);
String delimiter = action.getAttributesString("Delimiter", ",");
boolean append = action.getAttributesChar("Append", 'N') == 'Y';

Die verwendeten Hilfsmethoden stammen aus der Klasse BaseModule, von der die Modulklasse abgeleitet wurde.

Die Datenbankverbindung wird geöffnet:

// open database connection
datasqillDBConnection dbCon = openConnection(targetDatabaseID);

Abhängig vom Kommando wird die Transformation ausgeführt oder validiert:

// run command
switch (cmd) {
case "Run":
    // execute data transformation
    int count = doRun(dbCon, target, sqlQuery, delimiter, append);
    response.setRowsProcessed(count);
    break;
case "Validate":
    // validate sql query and model
    doValidate(response, dbCon, sqlQuery);
    break;
default:
    throw new DsModException(DsModErrorCodes.ERR_CODE_MISSING_COMMAND, ", got command " + cmd);
}

Abschließend wird die Response mit den Ergebnisdaten befüllt und zurückgegeben:

// add result data to response
long hostId = action.getExecutingHostId();
long moduleDuration = System.nanoTime() - startTime;
List<datasqillStatistic> statistics = new ArrayList<>();
statistics.add(new datasqillStatistic(hostId, "module_duration", moduleDuration));
response.setStatistics(statistics);

Die gesamte "execute"-Methode sieht dann so aus:

public datasqillTransformationAnswer execute(datasqillTransformationRequest request) {
    LOGGER.info("Starting module");
    // start time measuring and create response
    long startTime = System.nanoTime();
    datasqillTransformationAnswer response = new datasqillTransformationAnswer(request.getRequestId(), 0,
            "DsModFilterCsvFile", null, 0, null, -1, null, null);
    try {
        // read parameters
        Action action = getAction(request);
        String sqlQuery = getActionString(action);
        String cmd = getCommand(request);
        ObjectDs target = getTarget(action);
        List<ObjectDs> sources = getSources(action);
        Long targetDatabaseID = getDatabaseId(sources);
        String delimiter = action.getAttributesString("Delimiter", ",");
        boolean append = action.getAttributesChar("Append", 'N') == 'Y';
        // open database connection
        datasqillDBConnection dbCon = openConnection(targetDatabaseID);
        // run command
        switch (cmd) {
        case "Run":
            // execute data transformation
            int count = doRun(dbCon, target, sqlQuery, delimiter, append);
            response.setRowsProcessed(count);
            break;
        case "Validate":
            // validate sql query and model
            doValidate(response, dbCon, sqlQuery);
            break;
        default:
            throw new DsModException(DsModErrorCodes.ERR_CODE_MISSING_COMMAND, ", got command " + cmd);
        }
        // add result data to response
        long hostId = action.getExecutingHostId();
        long moduleDuration = System.nanoTime() - startTime;
        List<datasqillStatistic> statistics = new ArrayList<>();
        statistics.add(new datasqillStatistic(hostId, "module_duration", moduleDuration));
        response.setStatistics(statistics);
    } catch (DsModException e) {
        response.setErrorCode(e.getErrorCode());
        response.setErrorMessage(e.getErrorMessage());
    }
    LOGGER.info("Ending module");
    // return response
    return response;
}

Die Methode "doRun" zur Ausführung der Transformation ist relativ einfach. Sie öffnet die Ausgabedatei, führt die Datenbankabfrage aus und schreibt die Ergebnissätze zeilenweise in die Ausgabedatei:

private int doRun(datasqillDBConnection dbCon, ObjectDs target, String sqlQuery, String delimiter, boolean append)
        throws DsModException {
    // open the csv target file
    String directory = target.getObjectOwner();
    String filename = target.getObjectName();
    File file = new File(directory, filename);
    try (FileWriter fw = new FileWriter(file, append);
            BufferedWriter bw = new BufferedWriter(fw);
            PreparedStatement preparedStatement = dbCon.prepareStatement(sqlQuery);
            ResultSet resultSet = preparedStatement.executeQuery()) {
        // write csv header line
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
            bw.write(metaData.getColumnLabel(i));
            if (i < columnCount)
                bw.write(delimiter);
        }
        bw.write("\n");
        // transfer data row by row to csv target file
        int count = 0;
        while (resultSet.next()) {
            for (int i = 1; i <= columnCount; i++) {
                bw.write(resultSet.getString(i));
                if (i < columnCount)
                    bw.write(delimiter);
            }
            bw.write("\n");
            count++;
        }
        // and return number of rows processed
        return count;
    } catch (Exception e) {
        LOGGER.log(Level.SEVERE, e.getMessage(), e);
        throw new DsModException(DsModErrorCodes.ERR_CODE_TRANSFORM_PROBLEM);
    }
}

Die Methode zur Validierung heißt "doValidate". Sie stützt sich auf Bibliotheksfunktionen der Klasse "datasqillDBValidate" ab, die die eigentliche Arbeit übernehmen:

private boolean doValidate(datasqillTransformationAnswer response, datasqillDBConnection dbConSourceDb,
        String sqlQuery) {
    boolean result = datasqillDBValidate.validateSQLStatement(dbConSourceDb, response, sqlQuery);
    if (result)
        datasqillDBValidate.setStatementDependency(dbConSourceDb, response,
                "SELECT * FROM (" + sqlQuery + ") query WHERE 1=0");
    LOGGER.info("Validation result: " + result);
    return result;
}

Daneben werden noch Hilfsklassen verwendet. Zum Einen die bereits genannte Basisklasse BaseModule mit diversen Hilfsmethoden. Daneben noch eine Enumeration DsModErrorCode mit allen Fehlernummern und Fehlertexten und eine Exception DsModException.

Und schließlich gibt es natürlich noch die bereits vorgestellte "main"-Methode:

public static void main(String args[]) {
    datasqillProgramStart.init(args, new DsModDemo());
}

Das vollständige Beispiel kann hier heruntergeladen werden.

Deployment

Schließlich wollen wir unser Modul im Server einspielen und verwenden. Dazu müssen mehrere Schritte ausgeführt werden:

  1. Java Archiv des Moduls erzeugen und auf dem Server ablegen
  2. Konfigurationsparametern für das Modul im datasqill Repository einfügen

Modul bauen

Das Übersetzen des Moduls erfolgt am Einfachsten mit einem Build-Programm wie "Maven". Dazu wird ein Project Object Model kurz POM erstellt, das die erforderlichen Bibliotheken referenziert und Anweisungen zur Übersetzung enthält. Im Beispiel-Modul ist eine solche POM-Datei mit enthalten. Damit kann die Übersetzung und der Bau der Ergebnis-Artefakte per "Maven" angestoßen werden:

mvn package

Als Ergebnis entsteht ein Java Archiv (JAR-Datei), das auf dem Server im Library-Verzeichnis unter ~/lib/DsModDemo.jar abgelegt wird.

Konfiguration

Für ein Modul müssen Konfigurationsparameter im datasqill Repository hinterlegt werden.

Am Einfachsten kann dies über die Deployment-Werkzeuge von datasqill erledigt werden. Dazu müssen eine oder mehrere Json-Konfigurationsdateien angelegt werden, die ins Repository eingespielt werden.

Die Json-Konfigurationsdatei für das Modul DsModDemo enthält die jeweiligen Zieltabellen im Repository sowie den oder die einzufügenden Datensätze. Für unser Modul brauchen wir Einträge in zwei Tabellen

  • vv_sqts_module - Haupttabelle für Module
  • vv_sqts_action_attr_definition - Attributtabelle für Module

Die Tabelle vv_sqts_module, in die ein Datensatz eingefügt werden muss, hat folgende Spalten:

  • module_name: eindeutiger Modulbezeichner
  • module_name: der Modulname
  • module_type_id: der Modultyp
  • description: eine Beschreibung
  • module_command: das Modulkommando (Modulname bei Java Modulen)

Weiterhin muss für jedes Attribut ein Eintrag in der Tabelle vv_sqts_action_attr_definition erzeugt werden:

  • action_type: der Modulname
  • attr_name: der Attributname
  • attr_data_type: der Attributtyp (BOOLEAN, CHAR, CLOB, SQL, NUMBER)
  • attr_label: der Anzeigename in der datasqill GUI
  • attr_description: der Hilfetext in der datasqill GUI
  • attr_position: die Position des Attributs in der datasqill GUI

Eine Json-Konfigurationsdatei dsmoddemo.dsdb könnte für unser Beispielmodul so aussehen:

{
  objectList:
  [
    {
      dsdbFormat: 1
      deploymentType: version
      current:
      {
        version: 3.5
        data:
        [
          {
            target: vv_sqts_module
            columns: [ "module", "module_name", "module_type_id", "description", "module_command" ]
            rows: [
              [ "DemoModule", "Write CSV File (Demo)", 2, "Writes data from a database query to a csv file", "DsModDemo" ]
            ]
          }
          {
            target: vv_sqts_action_attr_definition
            columns: [ "action_type", "attr_name", "attr_label", "attr_data_type", "default_value", "attr_description", "attr_position" ]
            rows: [
              [ "DemoModule", "Delimiter", "Column Delimiter", "CHAR", ",", "Column delimiter (default is ',')", 1 ]
              [ "DemoModule", "Append", "Append", "BOOLEAN", "Y", "Append, do not truncate file", 2 ]
            ]
          }
        ]
      }
    }
  ]
}

Die Spalte module enthält eine eindeutige Bezeichnung für ein Modul. Sie wird in anderen Tabellen zur Referenzierung verwendet. Der Modulname wird in der GUI angezeigt. Der Modultyp ist 2, was für Java Modul steht. Eine vollständige Übersicht der unterstützten Typen findet sich in der Tabelle "vv_sqts_module_type".

Um die Konfigurationdatei im Server einzuspielen, kopiert man sie auf den Server, meldet man sich am Server per SSH an und ruft das Deployment-Skript auf:

deploySchema.sh < dsmoddemo.dsdb
Okt 24, 2020 10:41:34 PM de.softquadrat.datasqill.deployment.LogWrapDeployer insertData
FEIN: Add data to: vv_sqts_module
Okt 24, 2020 10:41:34 PM de.softquadrat.datasqill.deployment.LogWrapDeployer insertData
FEIN: Add data to: vv_sqts_action_attr_definition

Jetzt kann das Modul nach einem Neustart der GUI verwendet werden:

ActionDialog.png

Anmerkungen

Das vorgestellte Beispiel-Modul berücksichtigt keine Behandlung von Spezialfällen wie etwa Besonderheiten von Spaltenname ("case sensitive column names", "sql key word in column names"). Es gibt ein "professionelles" Modul DsModWriteFlatFile für das Erzeugen von CSV Dateien, das solche Spaltennamen korrekt behandelt und sie im SQL schützt ("quoting").

Und schließlich funktioniert die SQL Abfrage natürlich nur, wenn die Tabellen in einer gemeinsamen Datenbank liegen. Das stellt die Hilfsmethode "getSources" sicher. Sie wirft sowohl bei der Validierung als auch bei der Ausführung eine Exception, wenn sie feststellt, dass die Quelltabellen in verschiedenen Datenbanken liegen.