Search code examples
javaxmlintersystems-cache

Importing XMLs programmatically from Java: items are imported but the returned list is empty


This is a sample Java code which tries to use %SYSTEM.OBJ's LoadStream method to import the classes available in an XML export; import directives have been omitted for "brevity":

public final class Main2
{
    private static final String CACHEDB_HOST = "cachedb.host";
    private static final String CACHEDB_PORT = "cachedb.port";
    private static final String CACHEDB_USER = "cachedb.user";
    private static final String CACHEDB_PASSWORD = "cachedb.password";
    private static final String CACHEDB_NAMESPACE = "cachedb.namespace";
    private static final String LOADEDFILE = "loadedFile";

    private static final String CACHEDB_HOST_DEFAULT = "localhost";
    private static final String CACHEDB_PORT_DEFAULT = "1972";

    private static final String JDBC_URL_TEMPLATE = "jdbc:Cache://%s:%s/%s";

    private Main2()
    {
        throw new Error("instantiation not permitted");
    }

    public static void main(final String... args)
        throws IOException, CacheException
    {
        if (args.length == 0)
            throw new IllegalArgumentException("missing arguments");

        final Properties properties = new Properties();

        final Path path = Paths.get(args[0]).toRealPath();

        try (
            final Reader reader = Files.newBufferedReader(path);
        ) {
            properties.load(reader);
        }

        final String jdbcUrl = String.format(JDBC_URL_TEMPLATE,
            readProperty(properties, CACHEDB_HOST, CACHEDB_HOST_DEFAULT),
            readProperty(properties, CACHEDB_PORT, CACHEDB_PORT_DEFAULT),
            readProperty(properties, CACHEDB_NAMESPACE));

        final String user = readProperty(properties, CACHEDB_USER);
        final String password = readProperty(properties, CACHEDB_PASSWORD);

        final Path loadedFile = Paths.get(readProperty(properties, LOADEDFILE))
            .toRealPath();

        try (
            final CacheDb db = new CacheDb(jdbcUrl, user, password);
        ) {
            final GlobalCharacterStream stream
                = new GlobalCharacterStream(db.getDatabase());

            loadContent(stream, loadedFile);

            /*
             * Arguments for class "%SYSTEM.OBJ", class method "LoadStream"
             */
            final Dataholder[] arguments = new Dataholder[8];

            /*
             * Arguments ByRef
             *
             * Indices start at 1, not 0
             */
            final int[] byRefArgs = new int[2];

            // Arg 3: error log
            final StringHolder errorlog = new StringHolder("");
            byRefArgs[0] = 3;

            // Arg 4: list of loaded items
            final StringHolder loadedlist = new StringHolder("");
            byRefArgs[1] = 4;

            /*
             * Fill arguments
             */
            // arg 1: stream
            arguments[0] = Dataholder.create(stream);
            // arg 2: qspec; the default, therefore null
            arguments[1] = new Dataholder((String) null);
            // arg 3: errorlog
            arguments[2] = Dataholder.create(errorlog.value);
            // arg 4: loadedlist
            arguments[3] = Dataholder.create(loadedlist.value);
            // arg 5: listonly; we want true
            arguments[4] = Dataholder.create(Boolean.TRUE);
            // arg 6: selecteditems; nothing
            arguments[5] = Dataholder.create(null);
            // arg 7: displayname. For logging...
            arguments[6] = Dataholder.create("IMPORT");
            // arg 8: charset. Default is empty string, we'll assume UTF-8.
            arguments[7] = new Dataholder((String) null);

            // Now, make the call
            final Dataholder[] result = db.getDatabase().runClassMethod(
                "%SYSTEM.OBJ",
                "LoadStream",
                byRefArgs,
                arguments,
                Database.RET_PRIM
            );

            /*
             * The result normally has three members:
             *
             * - first is the status; and we need to do that:
             */
            db.getDatabase().parseStatus(result[0]);

            /*
             * - others are ByRef arguments
             */
            // FIXME: probably not ideal
            errorlog.set(result[1].getString());
            System.out.println("errorlog: " + errorlog.getValue());

            loadedlist.set(result[2].getString());
            System.out.println("loadedlist: " + loadedlist.getValue());
        }
    }

    private static void loadContent(final GlobalCharacterStream stream,
        final Path path)
        throws IOException, CacheException
    {
        final StringBuilder sb = new StringBuilder();

        try (
            final Reader reader = Files.newBufferedReader(path);
        ) {
            final char[] buf = new char[2048];
            int nrChars;

            while ((nrChars = reader.read(buf)) != -1)
                sb.append(buf, 0, nrChars);
        }

        stream._write(sb.toString());
    }

    private static String readProperty(final Properties properties,
        final String key)
    {
        final String ret = properties.getProperty(key);
        if (ret == null)
            throw new IllegalArgumentException("required property " + key
                + " is missing");
        return ret;
    }

    private static String readProperty(final Properties properties,
        final String key, final String defaultValue)
    {
        return properties.getProperty(key, defaultValue);
    }
}

Now, the code runs; in Studio, I see that the items are imported as well.

However the output is this (the caché install is in French, sorry):

Inventaire démarré le 02/12/2016 11:16:38
Classement du fichier IMPORT en tant que xml
Inventaire terminé.

errorlog: 
loadedlist: null

I cannot see which items have been imported.

What am I doing wrong?


Solution

  • Java bindings supports getting ByRef values, and you do it correct. But unfortunately there is one limitations, and you catch it here. With ByRef in Caché we can pass arrays, such like below

    array("name1")="value1"
    array("name2")="value2"
    

    But in Java we can't get such value, only if array was have value in a "root". Load method in a meanwhile, has code which transform array to list of values, and such value we already can get. So, as a workaround I can recommend to replace %GlobalCharacterStream to %FileCharacterStream, with some temporary filename with extension xml. And then we can use this filename in a Load method. So, after few changes, code should looks like:

        final FileCharacterStream stream = new FileCharacterStream(db);
    
        Dataholder[] args = new Dataholder[]{new Dataholder("xml")};
        Dataholder res = ((SysDatabase) db).runClassMethod("%File", "TempFilename", args, 0);
        stream._filenameSet(res.getString());
    
        loadContent(stream, path);
    
        final String remoteFileName = stream._filenameGet();
    
    
        /*
         * Arguments for class "%SYSTEM.OBJ", class method "Load"
         */
        final Dataholder[] arguments = new Dataholder[9];
    
        /*
         * Arguments ByRef
         *
         * Indices start at 1, not 0
         */
        final int[] byRefArgs = new int[3];
    
        // Arg 3: error log
        final StringHolder errorlog = new StringHolder("");
        byRefArgs[0] = 3;
    
        // Arg 4: list of loaded items
        final StringHolder loadedlist = new StringHolder("");
        byRefArgs[1] = 4;
    
        // Arg 9: description (?)
        final StringHolder description = new StringHolder("");
        byRefArgs[2] = 9;
    
        /*
         * Fill arguments
         */
        // arg 1: file name
        arguments[0] = Dataholder.create(remoteFileName);
        // arg 2: qspec; we want to ensure that compile works, at least
        arguments[1] = new Dataholder("d");
        // arg 3: errorlog
        arguments[2] = Dataholder.create(errorlog.value);
        // arg 4: loadedlist
        arguments[3] = Dataholder.create(loadedlist.value);
        // arg 5: listonly; no
        arguments[4] = Dataholder.create(Boolean.FALSE);
        // arg 6: selecteditems; nothing
        arguments[5] = Dataholder.create(null);
        // arg 7: displayname. For logging...
        arguments[6] = Dataholder.create("IMPORT.xml");
        // arg 8: charset. Default is empty string, we'll assume UTF-8.
        arguments[7] = new Dataholder((String) null);
        // arg 9: description (?)
        arguments[8] = Dataholder.create(description.value);
    
        // Now, make the call
        final Dataholder[] result = db.runClassMethod(
            "%SYSTEM.OBJ",
            "Load",
            byRefArgs,
            arguments,
            Database.RET_PRIM
        );
    
        /*
         * The result normally has three members:
         *
         * - first is the status; and we need to do that:
         */
        db.parseStatus(result[0]);
    
        /*
         * - others are ByRef arguments
         */
        errorlog.set(result[1].getString());
        System.out.println("errorlog: " + errorlog.getValue());
    
        loadedlist.set(result[2].getString());
        System.out.println("loadedlist: " + loadedlist.getValue());
    

    and as a result

     Load started on 02/12/2016 22:56:06
     Loading file IMPORT.xml as xml
     Imported class: Sample.Address
     Imported class: Sample.Person
     Load finished successfully.
    
     errorlog: 
     loadedlist: Sample.Address.cls,Sample.Person.cls