Jan Schreiber - Blog - Software - Bookmarks - About

jan.bio

Accessing Ontopia from PHP

At the Ontopia code camp at the TMRA 2009 Lars Marius talked about integration with Content Management Systems (CMS). Many CMSes available on the marked are written in PHP, so the question of how to integrate Ontopia which is written in Java with existing CMSes in PHP came up. This blog post shows one solution to the problem.

A while ago I saw that there is a PHP/Java bridge available. The bridge uses a streaming, XML-based network protocol, which can be used to connect a PHP (or some other scripting engine) to a Java virtual machine. As a result, you are able to call Java procedures from PHP or PHP procedures from Java. The good news is that you don’t even need a PHP extension to access Java. I thought that it would be fun to run tolog queries from PHP, and two hours later I managed to do this. This is how:

The PHP/Java bridge consists of two parts: A small include file written in PHP and some .jar-files that you have to put into your Tomcat lib directory. For testing purposes I did not even set up a Tomcat server, but started the bridge in the included servlet mode directly from the command line. Keep in mind that this was only an intial test, so I tried to take the shortest path to get things running. Use at your one risk!

I downloaded Ontopia 5.0.2 and extracted it into my home directory. Then I created a Postgresql database, wrote a database configuration file (see the ontopia docs) and imported my beetle topicmap. I downloaded the PHP/Java bridge, and put the following .jar-files into the lib-directory:

JavaBridge.jar php-script.jar php-servlet.jar

From the lib/ directory, I started the java bridge in servlet mode:

java -jar JavaBridge.jar SERVLET_LOCAL:8080

The result was a servlet listening on port 8080 on my local machine. Now for the PHP-part: I created a new directory and copied the Java.inc file from the PHP/Java bridge distribution into it. Then I wrote the following PHP code and saved it as test.php:

<?php
// Debugging is good for you
define ("JAVA_DEBUG", true);
// Tell PHP where and how to connect to Java
define ("JAVA_HOSTS", "127.0.0.1:8080");
define ("JAVA_PIPE_DIR", null);

require_once("Java.inc");

try {
    java_require('tmapi.jar;ontopia.jar;postgresql.jar');
    $factory = java("org.tmapi.core.TopicMapSystemFactory")->newInstance();

    // Tell TMAPI to use the postgresql database backend and my beetle topic map
    $factory->setProperty("net.ontopia.topicmaps.store", "rdbms");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.Database", "postgresql");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.ConnectionString",
        "jdbc:postgresql://localhost/tm_coleoptera");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.DriverClass", "org.postgresql.Driver");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.UserName", "jans");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.Password", "");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.ConnectionPool", "false");

    $sys = $factory->newTopicMapSystem();

    // Finally, we get a TopicMap-object
    $tm = $sys->getTopicMap('file:/Users/jans/labben/catcol2xtm/catcol.xtm');

    $topic = $tm->getTopicBySubjectIdentifier(
            $tm->createLocator("http://psi.entomologi.org/genus/carabus"));

    $name = $topic->getNames()->iterator()->next();

    // Print the name of the Topic that we fetched
    printf("Topic name is '%s'\n", $name->getValue());

} catch (JavaException $ex) {
    // Sometimes things go wrong...
    echo "An exception occured: $ex\n";
}
?>

The first lines define some constants to tell the PHP-part of the bridge how to access the Java servlet. Then, the client implementation of the communication protocol is included. By calling java_require(), I can tell the bridge where to look for the Java classes that I want to access from PHP. In this case, we need TMAPI, the Ontopia API and the JDBC-driver for postgresql. The java()-function is used to access Java-objects and returns a PHP-representation of the object. Once we have a PHP representation of a Java object, it’s possible to call its methods as if they were PHP methods, e.g. $sys = $factory->newTopicMapSystem();.

To run this code, I just invoked PHP from the command line:

$ php test.php

The previous example showed how to use TMAPI from PHP. This is how to run a Tolog query:

<?php
define ("JAVA_DEBUG", true);
define ("JAVA_HOSTS", "127.0.0.1:8080");
define ("JAVA_PIPE_DIR", null);

require_once("Java.inc");

try {
    java_require('tmapi.jar;ontopia.jar;postgresql.jar');
    $factory = java("org.tmapi.core.TopicMapSystemFactory")->newInstance();
    $factory->setProperty("net.ontopia.topicmaps.store", "rdbms");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.Database", "postgresql");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.ConnectionString",
        "jdbc:postgresql://localhost/tm_coleoptera");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.DriverClass", "org.postgresql.Driver");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.UserName", "jans");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.Password", "");
    $factory->setProperty("net.ontopia.topicmaps.impl.rdbms.ConnectionPool", "false");

    // Not the most elegant way of getting a TopicMapIF object,
    // guess you shouldn't do that at home.
    $proc = java("net.ontopia.topicmaps.query.utils.QueryUtils")->getQueryProcessor($tm->getWrapped());
    $query = <<<EOS
        using type for i"http://psi.topicmaps.org/iso13250/model/"
        select \$SUBCLASSES from type:supertype-subtype
        (i"http://psi.entomologi.org/genus/carabus" : type:supertype,
         \$SUBCLASSES : type:subtype)?';
EOS;
    $result = $proc->execute($query);
    $str = java('net.ontopia.topicmaps.utils.TopicStringifiers')->getDefaultStringifier();
    while(java_values($result->next())) {
        $row = $result->getValues();
        $arr = java_values($row);
        foreach($arr as $k => $v) {
            $val = java_values($v);
            $topicname = $str->toString($val);
            print("$topicname\n");
        }
    }
    $result->close();

} catch (JavaException $ex) {
    echo "An exception occured: $ex\n";
}

?>

I created an instance of a QueryProcessor object and run a query using wrapped Java objects. A new problem that arises here is how to convert a Java-object into a PHP value: In the while()-loop, $result->next() does not return a boolean, as you would except, but a PHP-representation of a Java boolean. The java_values() function does the dirty work and converts a Java-representation into a PHP value, which is a PHP boolean in this case. This function is also able to convert Java arrays into PHP arrays, as I did here: $arr = java_values($row);, where $row is a Java array and $arr is a PHP array. The elements of the resulting PHP arrays are still wrapped Java objects, so you have to call java_values() for these as well.

This is basically it. In a real world example, we would not use the provided container to run the bridge servlet, but something like Tomcat. See the documentation of the PHP/Java bridge for details. Disclaimer: This blog post has been written while I tried to keep up with Lars Marius’ talk, so it might be complete nonsense. Multitasking is hard and might lead to unexpected results in some cases.