/*
* 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):
* getModel() contributed by Netymon Pty Ltd on behalf of
* The Australian Commonwealth Government under contract 4500507038.
*
* [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;
// Java 2 standard packages
import java.net.URI;
import java.util.*;
import javax.transaction.xa.XAResource;
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.Node;
import org.jrdf.graph.URIReference;
// Locally written packages
import org.mulgara.content.Content;
import org.mulgara.content.ContentHandler;
import org.mulgara.content.ContentHandlerException;
import org.mulgara.content.ContentHandlerManager;
import org.mulgara.query.*;
import org.mulgara.resolver.spi.*;
import org.mulgara.store.nodepool.NodePool;
/**
* Resolves constraints in models defined by parsing a {@link Content} stream.
*
* @created 2004-10-04
*
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/02/22 08:16:17 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <a href="mailto:info@PIsoftware.com">Plugged In Software</a>
*
* @copyright ©2004 <a href="http://www.tucanatech.com/">Tucana Technology,
* Inc</a>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public abstract class ContentResolver implements Resolver {
/**
* The session that this resolver is associated with.
*/
protected final ResolverSession resolverSession;
/**
* Map from the {@link URIReference} of each document ever parsed by this
* resolver to the corresponding {@link Content}.
*/
protected final Map<URIReference,Content> contentMap = new HashMap<URIReference,Content>();
protected final ContentHandlerManager contentManager;
/** Current system resolver */
@SuppressWarnings("unused")
private final Resolver systemResolver;
/** Logger. */
private static final Logger logger = Logger.getLogger(ContentResolver.class.getName());
//
// Constructors
//
/**
* Construct a ContentResolver.
*
* @param resolverSession the session this resolver is associated with - unused for now.
* @param contentManager the available {@link ContentHandler}s
* @throws IllegalArgumentException if <var>resolverSession</var> is
* <code>null</code>
*/
protected ContentResolver(ResolverSession resolverSession,
Resolver systemResolver,
ContentHandlerManager contentManager) {
// Validate parameters
if (resolverSession == null) {
throw new IllegalArgumentException("Null 'resolverSession' parameter");
}
if (systemResolver == null) {
throw new IllegalArgumentException("Null 'systemResolver' parameter");
}
if (contentManager == null) {
throw new IllegalArgumentException("Null 'contentManager' parameter");
}
// Initialize fields
this.resolverSession = resolverSession;
this.systemResolver = systemResolver;
this.contentManager = contentManager;
}
//
// Methods implementing Resolver
//
/**
* Create a model by creating empty {@link Content}.
*
* @param model {@inheritDoc}. In this case it should always be a URL
* referencing {@link Content} outside the database.
* @param modelTypeURI {@inheritDoc}. This field is ignored, because the {@link Content} is external.
*/
public void createModel(long model, URI modelTypeURI) throws ResolverException {
if (logger.isDebugEnabled()) logger.debug("Create content model " + model);
throw new ResolverException("Creation of external documents not implemented");
}
/**
* @return a {@link DummyXAResource} with a 10 second transaction timeout
*/
public XAResource getXAResource() {
return new DummyXAResource(10); // seconds before transaction timeout
}
/**
* Insert or delete RDF statements from a model stored in a file.
*/
public void modifyModel(long model, Statements statements, boolean occurs) throws ResolverException {
if (logger.isDebugEnabled()) logger.debug("Modify external document model " + model);
Content content = toContent(model);
assert content != null;
try {
ContentHandler contentHandler = contentManager.getContentHandler(content);
contentHandler.serialize(statements, content, resolverSession, null);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
URI u = content.getURI();
if (u != null) throw new ResolverException("Unable to serialize " + content.getURI(), e);
try {
throw new ResolverException("Unable to serialize stream with type: " + content.getContentType(), e);
} catch (NotModifiedException e1) {
throw new ResolverException("Unable to serialize stream", e);
}
}
}
/**
* Remove the file containing the model.
*/
public void removeModel(long model) throws ResolverException {
if (logger.isDebugEnabled()) logger.debug("Remove model " + model);
throw new ResolverException("Deletion of external documents not implemented");
}
/**
* Resolve a constraint against an RDF/XML document.
* Resolution is by filtration of a stream, and thus very slow.
*/
public Resolution resolve(Constraint constraint) throws QueryException {
if (logger.isDebugEnabled()) logger.debug("Resolve " + constraint);
// Validate parameters
if (constraint == null) throw new IllegalArgumentException("constraint null");
else if (!(constraint.getModel() instanceof LocalNode)) {
throw new QueryException("Constraint model can't be variable");
}
// Convert the constraint's model to a URI reference
URIReference modelURIReference;
long modelNode;
try {
modelNode = ((LocalNode) constraint.getModel()).getValue();
Node node = resolverSession.globalize(modelNode);
if (!(node instanceof URIReference)) {
throw new QueryException("Constraint model " + node +
" isn't a URI reference");
}
modelURIReference = (URIReference) node;
}
catch (GlobalizeException e) {
throw new QueryException("Couldn't globalize model for " + constraint, e);
}
assert modelNode != NodePool.NONE;
assert modelURIReference != null;
// Find or create the Content instance representing this external document
Content content = contentMap.get(modelURIReference);
if (content == null) {
try {
content = toContent(modelNode);
}
catch (ResolverException e) {
throw new QueryException("Unable to parse " + modelURIReference, e);
}
contentMap.put(modelURIReference, content);
}
assert content != null;
assert contentMap.get(modelURIReference) == content;
Statements statements = toStatements(content);
assert statements != null;
// Generate the resolution
// TODO: implement switching based on MIME type
return new StatementsWrapperResolution(
constraint,
statements,
true // a definitive and complete resolution
);
}
//
// Internal methods
//
/**
* @param content the stream to parse, never <code>null</code>
* @return RDF statements parsed from the <var>content</var>
*/
private Statements toStatements(Content content) throws QueryException {
// See if any of the content handlers recognize the URI extension
try {
ContentHandler contentHandler = contentManager.getContentHandler(content);
return contentHandler.parse(content, resolverSession);
} catch (NotModifiedException e) {
throw new QueryException("Unable to parse " + out(content), e);
} catch (CorruptContentException e) {
// This is the right content type, but the content is corrupt
throw new QueryException("Unable to parse " + out(content), e);
} catch (ContentHandlerException contentHandlerException) {
// Continue trying other content handlers
}
// See if any of the content handlers can parse the input, despite the
// unfamiliar extension
try {
// Attempt to parse the content of the file into statements
return contentManager.blindParse(content, resolverSession);
} catch (NotModifiedException e) {
throw new QueryException("Unable to parse " + out(content), e);
} catch (CorruptContentException e) {
// This is the right content type, but the content is corrupt
throw new QueryException("Unable to parse " + out(content), e);
} catch (ContentHandlerException e) {
// Continue trying other content handlers
}
// Couldn't obtain a content handler for this URI
throw new QueryException("Unable to parse " + out(content));
}
public void abort() {
// I don't believe there is anything to do here. It is possible that we may
// need to close file handles or clear caches.
}
/**
* Creates a label for the content.
* @param content The content to describe.
* @return A string containing a description of what can be found in the content.
*/
private String out(Content content) {
try {
return content.getURI() == null ? "stream of type: " + content.getContentType() : content.getURI().toString();
} catch (NotModifiedException e) {
return "stream of unknown type";
}
}
//
// SPI methods
//
/**
* Convert a local node representing an external document into
* {@link Content}.
*
* @param modelNode
* @return the {@link Content} of the external document represented by the
* <var>modelNode</var>
* @throws ResolverException if the conversion cannot be performed
*/
protected abstract Content toContent(long modelNode) throws ResolverException;
}