An example of a holdings query program using Java and JENA
This tutorial explores how you might integrate data from the Talis Platform Holdings REST API into your Java application.
It uses the libraries provided by the JENA project (an open-source project funded by HP to provide a Semantic Web Framework for Java). You will need to download these from http://jena.sourceforge.net and include them in your project’s classpath in order for the example here to run. JENA will provide us a SPARQL query interface for our Java application.
Our application is going to do one thing in two different ways – provide a machine-to-machine (MMI) style implementation and a human readable implementation of a Holdings data query (by ISBN).
Let’s take the MMI interface first, as this is the simplest of the two operations and doesn’t require JENA libraries to process the RDF-XML that is returned by the API. There really are only three steps required to implement:
- Build a URI incorporating your Talis Platform API key and the ISBN you want to query
- Open a connection to that URL
- Read the data (we’ll output it to the console in our app, in the real world you could pipe it into another interface or transform it with an XSLT).
The first step is simple – we’ll define a base URI as a String and append our API key and ISBN to that, by defining a method called getHoldings() which accepts the API key and ISBN as parameters:
private static final String BASE_URL = "http://api.talis.com/1/bib/holdings?output=rdf";
/**
* Retrieves a result from the holdings lookup service in RDF-XML format
* @param apiKey
* @param isbn
* @return
* @throws Exception
*/
public InputStream getHoldings(String apiKey, String isbn) throws Exception
{
StringBuilder urlString = new StringBuilder();
urlString.append(BASE_URL);
urlString.append("&api_key=");
urlString.append(apiKey);
urlString.append("&isbn=");
urlString.append(isbn);
...
}
Now step 2 – open a connection to the URL we’ve just built and return the results in an InputStream object – we’re going to add the following code into our getHoldings() method (around where the “...” was in the previous listing):
URL url = new URL(urlString.toString());
url.openConnection();
return url.openStream();
Step 3 – reading the data and piping to an appropriate location – in our example, we’re going to throw it straight out to the System console, using the inbuilt Java XML APIs. We’ll put this code into our class’ main method so the application can be run from the command line:
public static void main(String[] args) throws Exception
{
Holdings holdings = new Holdings();
// build document
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document d = builder.parse(holdings.getHoldings(args[1],args[2]));
// transform result to a String
Source source = new DOMSource(d);
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(source, result);
// print out result
System.out.println("Document: \n"+stringWriter.getBuffer().toString());
}
Here you can see we are calling our getHoldings() method using arguments 1 and 2 supplied on the command line (argument 0 is reserved for determining the mode in our full program listing)
And that’s about it. Running the program will produce similar output to the following (truncated to one result for clarity):
Document:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dctype="http://purl.org/dc/dcmitype/"
xmlns:frbr="http://purl.org/vocab/frbr/core#"
xmlns:h="http://schemas.talis.com/2005/holdings/schema#">
<h:Holding>
<h:resource>
<frbr:Manifestation>
<dc:identifier>urn:isbn:0596000480</dc:identifier>
</frbr:Manifestation>
</h:resource>
<h:collection>
<dctype:Collection dc:identifier="n006.inst" dc:title="Bedfordshire Libraries"/>
</h:collection>
</h:Holding>
</rdf:RDF>
Now we’re going to use JENA to produce a human-readable representation of the same data (plus a link representing an OPAC deep link into the holding institution’s OPAC). We’ll need to:
- Define a SPARQL query
- Call the getHoldings() method as before, but pipe the results into an in-memory JENA RDF Model
- Apply the SPARQL query to the model and output the results
Step 1: We’ll use JENA’s QueryFactory class to create a Query object for us:
private static final Query SPARQL_QUERY = QueryFactory.create(
"PREFIX dctype: <http://purl.org/dc/dcmitype/> " +
"PREFIX dc: <http://purl.org/dc/elements/1.1/> " +
"PREFIX h: <http://schemas.talis.com/2005/holdings/schema#> " +
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
"SELECT ?title ?id WHERE {?x rdf:type dctype:Collection. " +
"?x dc:title ?title. ?x dc:identifier ?id}");
Don’t worry too much about the syntax for now, you can read up on the SPARQL query language here. Suffice to say, this query is extracting the title and id fields from our RDF data.
Step 2: Building an in-memory RDF Model using JENA – this is fairly trivial using the JENA framework by using the following code to pipe the output of the getHoldings() method into JENA’s Model.read() method:
Model model = ModelFactory.createDefaultModel();
model.read(holdings.getHoldings(args[1],args[2]),"");
Step 3: Executing the SPARQL query and outputting the results. First, we’ll create a JENA QueryExecution object:
QueryExecution qe = QueryExecutionFactory.create(SPARQL_QUERY, model);
Next some code to execute the query and format the results. Note the finally{} block that always closes the QueryExecution object, no matter what exceptions are thrown in the try block. This is analogous to a JDBC connection.close() call to tidy up database connections:
try
{
ResultSet results = qe.execSelect();
while (results.hasNext())
{
QuerySolution soln = results.nextSolution();
RDFNode title = soln.get("title");
RDFNode id = soln.get("id");
StringBuilder result = new StringBuilder();
result.append(title.toString());
result.append(" holds a copy of this book (http://api.talis.com/1/node/items/");
result.append(id.toString());
result.append("/bib?api_key=");
result.append(args[1]);
result.append("&isbn=");
result.append(args[2]);
result.append(")");
System.out.println(result.toString());
}
}
finally
{
qe.close();
}
The output should look something link this:
Cork County Library holds a copy of this book (http://api.talis.com/1/node/items/5048.inst/bib?api_key=abc123&isbn=0596000480)
Liverpool Libraries & Information Services holds a copy of this book (http://api.talis.com/1/node/items/0002.inst/bib?api_key=abc123&isbn=0596000480)
Darlington Library holds a copy of this book (http://api.talis.com/1/node/items/3035.inst/bib?api_key=abc123&isbn=0596000480)
Usage (from the command line – remember to add in your classpath declaration!):
EXAMPLE: java com.talis.holdings.Holdings mode apikey isbn
RDF MMI OUTPUT: java com.talis.holdings.Holdings rdf abc123 0596000480
HUMAN OUTPUT: java com.talis.holdings.Holdings human abc123 0596000480
The program listing in full:
/**
* The person or persons who have associated work with this document (the "Dedicator" or "Certifier")
* hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is
* in the public domain of the country from which the work is published, or (b) hereby dedicates
* whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to
* the public domain. A certifier, moreover, dedicates any copyright interest he may have in the
* associated work, and for these purposes, is described as a "dedicator" below.
*
* A certifier has taken reasonable steps to verify the copyright status of this work. Certifier
* recognizes that his good faith efforts may not shield him from liability if in fact the work
* certified is not in the public domain.
*
* Dedicator makes this dedication for the benefit of the public at large and to the detriment of the
* Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights under copyright law, whether vested or
* contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the
* relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work.
*
* Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced,
* distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any
* purpose, commercial or non-commercial, and in any way, including by methods that have not yet been
* invented or conceived.
*/
package com.talis.holdings;
import org.w3c.dom.*;
import java.net.URL;
import java.io.StringWriter;
import java.io.InputStream;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.query.*;
/**
* Can be used to extract holdings information from the Talis Platform
*/
public class Holdings
{
private static final String BASE_URL = "http://api.talis.com/1/bib/holdings?output=rdf";
private static final Query SPARQL_QUERY = QueryFactory.create(
"PREFIX dctype: <http://purl.org/dc/dcmitype/> " +
"PREFIX dc: <http://purl.org/dc/elements/1.1/> " +
"PREFIX h: <http://schemas.talis.com/2005/holdings/schema#> " +
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
"SELECT ?title ?id WHERE {?x rdf:type dctype:Collection. " +
"?x dc:title ?title. ?x dc:identifier ?id}");
/**
* Main method for holdings class
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception
{
if (args==null||args.length!=3)
{
System.out.print("Usage:\njava com.talis.Holdings mode apiKey " +
"isbn\nmode=rdf (RDF-XML output) or human (human readable " +
"output)\napiKey=Your API key, available at www.talis.com/tdn\n" +
"isbn=A valid ISBN identifier");
return;
}
Holdings holdings = new Holdings();
String mode = args[0];
if (mode.toUpperCase().equals("RDF"))
{
// build document
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document d = builder.parse(holdings.getHoldings(args[1],args[2]));
// transform result to a String
Source source = new DOMSource(d);
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(source, result);
// print out result
System.out.println("Document: \n"+stringWriter.getBuffer().toString());
}
else if (mode.toUpperCase().equals("HUMAN"))
{
Model model = ModelFactory.createDefaultModel();
model.read(holdings.getHoldings(args[1],args[2]),"");
QueryExecution qe = QueryExecutionFactory.create(SPARQL_QUERY, model);
try
{
ResultSet results = qe.execSelect();
while (results.hasNext())
{
QuerySolution soln = results.nextSolution();
RDFNode title = soln.get("title");
RDFNode id = soln.get("id");
StringBuilder result = new StringBuilder();
result.append(title.toString());
result.append(" holds a copy of this book (http://api.talis.com/1/node/items/");
result.append(id.toString());
result.append("/bib?api_key=");
result.append(args[1]);
result.append("&isbn=");
result.append(args[2]);
result.append(")");
System.out.println(result.toString());
}
}
finally
{
qe.close();
}
}
else
{
System.out.println("Unrecognised mode: "+mode);
}
}
/**
* Retrieves a result from the holdings lookup service in RDF-XML format
* @param apiKey
* @param isbn
* @return
* @throws Exception
*/
public InputStream getHoldings(String apiKey, String isbn) throws Exception
{
StringBuilder urlString = new StringBuilder();
urlString.append(BASE_URL);
urlString.append("&api_key=");
urlString.append(apiKey);
urlString.append("&isbn=");
urlString.append(isbn);
URL url = new URL(urlString.toString());
url.openConnection();
return url.openStream();
}
}



A question
Hi,
Do you know how someone may change the code so that the output appears on
JTextArea instead of standard output?
Hi, You could try replacing
Hi,
You could try replacing the System.out.println method calls with a call to JTextArea's setText method instead.
There's a basic JTextArea tutorial here: http://java.sun.com/docs/books/tutorial/uiswing/components/textarea.html
Ian