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.