package net.fortytwo.sesametools.ldserver;
import info.aduna.iteration.CloseableIteration;
import org.openrdf.model.IRI;
import org.openrdf.model.Namespace;
import org.openrdf.model.Statement;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.rio.RDFFormat;
import org.openrdf.sail.Sail;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailException;
import org.restlet.data.MediaType;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Information and non-information resources are distinguished by the suffix of the resource's URI:
* information resource URIs end in .rdf or .trig,
* while non-information resources have no such suffix
* (and LinkedDataServer will not make statements about such URIs).
* A request for an information resource is fulfilled with the resource itself. No content negotiation occurs.
* A request for a non-information resource is fulfilled with a 303-redirect
* to an information resource of the appropriate media type.
*
* @author Joshua Shinavier (http://fortytwo.net)
*/
public class WebResource extends ServerResource {
private static final Logger logger = Logger.getLogger(WebResource.class.getName());
enum WebResourceCategory {
INFORMATION_RESOURCE, NON_INFORMATION_RESOURCE
}
protected String selfURI;
protected String hostIdentifier;
protected String baseRef;
protected String subjectResourceURI;
protected String typeSpecificId;
protected WebResourceCategory webResourceCategory;
protected Sail sail;
protected IRI datasetURI;
protected Optional<RDFFormat> format;
public WebResource() {
super();
getVariants().addAll(RDFMediaTypes.getRDFVariants());
}
public void preprocessingHook() {
// Do nothing unless overridden
}
public void postProcessingHook() {
// Do nothing unless overridden
}
@Get
public Representation get(final Variant variant) {
selfURI = this.getRequest().getResourceRef().toString();
int i = selfURI.lastIndexOf(".");
if (i > 0) {
format = RDFFormat.matchFileName(selfURI, null);
}
if (!format.isPresent()) {
webResourceCategory = WebResourceCategory.NON_INFORMATION_RESOURCE;
getVariants().addAll(RDFMediaTypes.getRDFVariants());
} else {
webResourceCategory = WebResourceCategory.INFORMATION_RESOURCE;
getVariants().add(RDFMediaTypes.findVariant(format.get()));
hostIdentifier = this.getRequest().getResourceRef().getHostIdentifier();
baseRef = this.getRequest().getResourceRef().getBaseRef().toString();
subjectResourceURI = selfURI.substring(0, i);
typeSpecificId = subjectResourceURI.substring(baseRef.length());
datasetURI = LinkedDataServer.getInstance().getDatasetURI();
sail = LinkedDataServer.getInstance().getSail();
}
MediaType type = variant.getMediaType();
switch (webResourceCategory) {
case INFORMATION_RESOURCE:
return representInformationResource();
case NON_INFORMATION_RESOURCE:
return representNonInformationResource(type);
default:
throw new IllegalStateException("no such resource type: " + webResourceCategory);
}
}
private Representation representInformationResource() {
try {
preprocessingHook();
IRI subject = sail.getValueFactory().createIRI(subjectResourceURI);
Representation result = getRDFRepresentation(subject, format.get());
postProcessingHook();
return result;
} catch (Throwable t) {
t.printStackTrace();
return null;
}
}
private Representation representNonInformationResource(final MediaType type) {
RDFFormat format = RDFMediaTypes.findRdfFormat(type);
if (null == format) {
throw new IllegalStateException("no RDF format for media type " + type);
}
String suffix = format.getDefaultFileExtension();
if (null == suffix) {
throw new IllegalStateException("no suffix for RDF format " + type);
}
getResponse().redirectSeeOther(selfURI + "." + suffix);
return null;
}
private void addIncidentStatements(final org.openrdf.model.Resource vertex,
final Collection<Statement> statements,
final SailConnection c) throws SailException {
// Select outbound statements
CloseableIteration<? extends Statement, SailException> stIter
= c.getStatements(vertex, null, null, false);
try {
while (stIter.hasNext()) {
Statement s = stIter.next();
if (null == s.getContext()) {
statements.add(s);
}
}
} finally {
stIter.close();
}
// Select inbound statements
stIter = c.getStatements(null, null, vertex, false);
try {
while (stIter.hasNext()) {
Statement s = stIter.next();
if (null == s.getContext()) {
statements.add(s);
}
}
} finally {
stIter.close();
}
}
// Note: a SPARQL query might be more efficient in some applications
private void addSeeAlsoStatements(final org.openrdf.model.Resource subject,
final Collection<Statement> statements,
final SailConnection c,
final ValueFactory vf) throws SailException {
Set<IRI> contexts = new HashSet<>();
CloseableIteration<? extends Statement, SailException> iter
= c.getStatements(subject, null, null, false);
try {
while (iter.hasNext()) {
Statement st = iter.next();
org.openrdf.model.Resource context = st.getContext();
if (null != context && context instanceof IRI && context.toString().startsWith(hostIdentifier)) {
contexts.add((IRI) context);
}
}
} finally {
iter.close();
}
iter = c.getStatements(null, null, subject, false);
try {
while (iter.hasNext()) {
Statement st = iter.next();
org.openrdf.model.Resource context = st.getContext();
if (null != context && context instanceof IRI && context.toString().startsWith(hostIdentifier)) {
contexts.add((IRI) context);
}
}
} finally {
iter.close();
}
for (IRI r : contexts) {
statements.add(vf.createStatement(subject, RDFS.SEEALSO, r));
}
}
private void addDocumentMetadata(final Collection<Statement> statements,
final ValueFactory vf) throws SailException {
// Metadata about the document itself
IRI docURI = vf.createIRI(selfURI);
statements.add(vf.createStatement(docURI, RDF.TYPE, vf.createIRI("http://xmlns.com/foaf/0.1/Document")));
statements.add(vf.createStatement(docURI, RDFS.LABEL,
vf.createLiteral("" + format.get().getName() + " description of resource '"
+ typeSpecificId + "'")));
// Note: we go to the trouble of special-casing the dataset URI, so that
// it is properly rewritten, along with all other TwitLogic resource
// URIs (which are rewritten through the Sail).
if (null != datasetURI) {
statements.add(vf.createStatement(docURI, RDFS.SEEALSO, datasetURI));
}
}
private String resourceDescriptor() {
return "resource";
}
private Representation getRDFRepresentation(final IRI subject,
final RDFFormat format) {
try {
Collection<Namespace> namespaces = new LinkedList<>();
Collection<Statement> statements = new LinkedList<>();
SailConnection c = sail.getConnection();
try {
// Add statements incident on the resource itself.
addIncidentStatements(subject, statements, c);
// Add virtual statements about named graphs.
addSeeAlsoStatements(subject, statements, c, sail.getValueFactory());
// Add virtual statements about the document.
addDocumentMetadata(statements, sail.getValueFactory());
// Select namespaces, for human-friendliness
try (CloseableIteration<? extends Namespace, SailException> nsIter = c.getNamespaces()) {
while (nsIter.hasNext()) {
namespaces.add(nsIter.next());
}
}
} finally {
c.close();
}
return new RDFRepresentation(statements, namespaces, format);
} catch (Throwable t) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(bos));
logger.log(Level.WARNING,
"failed to create RDF representation (stack trace follows)\n" + bos.toString(), t);
return null;
}
}
}