/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.content.rdfxml;
// Java 2 standard packages
import java.io.*;
import java.util.*;
// Third party packages
import org.apache.log4j.Logger; // Apache Log4J
// Locally written packages
import org.mulgara.content.Content;
import org.mulgara.content.NotModifiedException;
import org.mulgara.content.NotModifiedTuplesException;
import org.mulgara.query.Cursor;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.resolver.spi.ResolverSession;
import org.mulgara.resolver.spi.Statements;
import org.mulgara.resolver.spi.StatementsWrapperResolution;
import org.mulgara.store.tuples.AbstractTuples;
import org.mulgara.store.tuples.Tuples;
/**
* Parses an {@link InputStream} into {@link Statements}.
*
* This particular implementation is complicated by the need to adapt the Jena
* ARP RDF/XML <q>push</q> parser to be a <q>pull</q> parser instead.
*
* @created 2004-04-02
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
* @version $Revision: 1.8 $
* @modified $Date: 2005/01/05 04:58:03 $ @maintenanceAuthor $Author: newmana $
* @company <a href="mailto:info@PIsoftware.com">Plugged In Software</a>
* @copyright © 2004 <a href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class RDFXMLStatements extends AbstractTuples implements Statements {
/**
* Logger.
*/
private static final Logger logger =
Logger.getLogger(RDFXMLStatements.class.getName());
/**
* The RDF/XML formatted document.
*/
private Content content;
/**
* The session used to globalize the RDF nodes from the stream.
*/
private ResolverSession resolverSession;
/**
* The current row.
*
* if the cursor is not on a row, this will be <code>null</code>
*/
private long[] triple;
private Parser parser = null;
private long rowCount;
private boolean rowCountIsValid = false;
//
// Constructors
//
/**
* Construct an RDF/XML stream parser.
*
* @param content the RDF/XML content
* @param resolverSession session against which to localize RDF nodes
* @throws IllegalArgumentException if <var>inputStream</var> or
* <var>resolverSession</var> are <code>null</code>
*/
RDFXMLStatements(Content content, ResolverSession resolverSession)
{
// Validate "content" parameter
if (content == null) {
throw new IllegalArgumentException( "Null \"content\" parameter");
}
// Validate "resolverSession" parameter
if (resolverSession == null) {
throw new IllegalArgumentException("Null \"resolverSession\" parameter");
}
// Initialize fields
this.content = content;
this.resolverSession = resolverSession;
// Fix the magical column names for RDF statements
setVariables(new Variable[] { new Variable("subject"),
new Variable("predicate"),
new Variable("object") });
}
//
// Methods implementing Statements
//
public long getSubject() throws TuplesException
{
return getColumnValue(0);
}
public long getPredicate() throws TuplesException
{
return getColumnValue(1);
}
public long getObject() throws TuplesException
{
return getColumnValue(2);
}
//
// Methods implementing AbstractTuples
//
/**
* {@inheritDoc}
*
* Non-zero length <var>prefix</var> values don't need to be supported by
* this class because prefix filtration is implemented by the
* {@link StatementsWrapperResolution} which the existing external resolvers
* always apply to their content before returning it.
*
* @param prefix {@inheritDoc}; for this particular implementation, non-zero
* length prefixes are not supported
* @throws {@inheritDoc}; also if <var>prefix</var> is non-zero length
*/
public void beforeFirst(long[] prefix, int suffixTruncation)
throws TuplesException
{
if (logger.isDebugEnabled()) {
logger.debug("Before first");
}
// Validate "prefix" parameter
if (prefix == null) {
throw new IllegalArgumentException("Null \"prefix\" parameter");
}
if (prefix.length != 0) {
throw new TuplesException(
getClass() + ".beforeFirst isn't implemented for non-zero length prefix"
);
}
// Validate "suffixTruncation" parameter
if (suffixTruncation != 0) {
throw new IllegalArgumentException("Null \"suffixTruncation\" parameter");
}
// Shut down any existing parsing thread
if (parser != null) {
stopThread();
}
// Create the parser and start the parsing thread
try {
parser = new Parser(content, resolverSession);
}
catch (NotModifiedException e) {
throw new NotModifiedTuplesException(e);
}
parser.start();
// TODO skip forward to the first triple that matches prefix
}
/**
* The cursor position isn't cloned by this method.
*/
public Object clone()
{
RDFXMLStatements cloned = (RDFXMLStatements) super.clone();
// Copy immutable fields by reference
cloned.content = content;
cloned.resolverSession = resolverSession;
// The cursor position is not cloned.
cloned.triple = null;
cloned.parser = null;
return cloned;
}
/**
* Close the RDF/XML formatted input stream.
*/
public void close() throws TuplesException
{
stopThread();
}
/**
* @param column 0 for the subject, 1 for the predicate, 2 for the object
*/
public long getColumnValue(int column) throws TuplesException
{
if (triple == null) {
throw new TuplesException("There is no current row");
}
// Validate "column" parameter
if (column < 0 || column > 2) {
throw new TuplesException("No such column " + column);
}
return triple[column];
}
public List<Tuples> getOperands() {
return Collections.emptyList();
}
public int getRowCardinality() throws TuplesException {
long statementCount;
if (rowCountIsValid) {
statementCount = rowCount;
} else {
Parser p;
boolean newParser;
if (parser != null) {
// Use the existing parser.
p = parser;
newParser = false;
} else {
// Create a new parser.
try {
p = new Parser(content, resolverSession);
p.start();
}
catch (NotModifiedException e) {
throw new NotModifiedTuplesException(e);
}
newParser = true;
}
// We can do this since the queue holds more than two triples.
try {
synchronized (p) {
while (p.getStatementCount() < 2 && !p.isStatementCountTotal()) {
try {
// Wait on the parser for changes to the statement count or
// completion status.
p.wait();
} catch (InterruptedException ex) {
throw new TuplesException("Abort");
}
}
statementCount = p.getStatementCount();
}
} catch (TuplesException ex) {
p.abort();
if (!newParser) {
// We just aborted the main parser, so nullify the reference.
parser = null;
}
throw ex; // rethrow.
} finally {
if (newParser) {
// Stop the thread.
p.abort();
}
}
}
// Convert the statement count into a cardinality class
return statementCount == 0 ? Cursor.ZERO :
statementCount == 1 ? Cursor.ONE :
Cursor.MANY;
}
public long getRowCount() throws TuplesException
{
if (!rowCountIsValid) {
if (parser != null && parser.isStatementCountTotal()) {
// Get the statement count from the parser.
rowCount = parser.getStatementCount();
} else {
// Create a new parser.
Parser p;
try {
p = new Parser(content, resolverSession);
}
catch (NotModifiedException e) {
throw new NotModifiedTuplesException(e);
}
assert p != null;
// Consume the entire file.
p.start();
try {
rowCount = p.waitForStatementTotal();
} finally {
p.abort();
}
}
rowCountIsValid = true;
}
return rowCount;
}
public long getRowUpperBound() throws TuplesException {
// If the row count isn't yet available, return an absurdly huge value
return parser != null && parser.isStatementCountTotal() ?
parser.getStatementCount() : Long.MAX_VALUE;
}
/** Guess at a large number */
private static final Long LARGE_FILE_SIZE = 1000000L;
public long getRowExpectedCount() throws TuplesException {
// If the row count isn't yet available, return an absurdly huge value
return parser != null && parser.isStatementCountTotal() ?
parser.getStatementCount() : LARGE_FILE_SIZE;
}
public boolean hasNoDuplicates() throws TuplesException {
return false;
}
public boolean isColumnEverUnbound(int column) throws TuplesException {
switch (column) {
case 0: case 1: case 2:
return false;
default:
throw new TuplesException("No such column " + column);
}
}
public boolean next() throws TuplesException
{
if (parser == null) {
// no current row
return false;
}
try {
triple = parser.getTriple();
} catch (TuplesException ex) {
stopThread();
throw ex; // rethrow
}
if (triple == null) {
// Hit the end of the file.
assert parser.isStatementCountTotal();
rowCount = parser.getStatementCount();
rowCountIsValid = true;
stopThread();
}
return triple != null;
}
/**
* Stops the thread if it is running, and clears the current row.
*/
private void stopThread()
{
if (parser != null) {
parser.abort();
parser = null;
}
triple = null;
}
}