[VIVO-1312] Implement Linked Data Fragment Server (#53)

* [VIVO-1312] Linked Data Fragments initial implementation

* [VIVO-1312] Use known ontologies in the prefixes config

* [VIVO-1312] Simplify SPARQL as when restricted to specific values, they don’t need ordering

* [VIVO-1312] Freemarker fixes

* [VIVO-1312] Remove blank nodes

* [VIVO-1312] Add access control header and standard prefixes for TPF

* [VIVO-1312] Render literals in form so that they will work on resubmit

* [VIVO-1312] Minor template update

* [VIVO-1312] Minor template update
This commit is contained in:
grahamtriggs 2016-12-23 21:55:08 +00:00 committed by GitHub
parent e9cb3de52e
commit 68a462b05a
63 changed files with 5215 additions and 0 deletions

View file

@ -0,0 +1,119 @@
package org.linkeddatafragments.config;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.linkeddatafragments.datasource.IDataSourceType;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Reads the configuration of a Linked Data Fragments server.
*
* @author Ruben Verborgh
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class ConfigReader {
private final Map<String, IDataSourceType> dataSourceTypes = new HashMap<>();
private final Map<String, JsonObject> dataSources = new HashMap<>();
private final Map<String, String> prefixes = new HashMap<>();
private final String baseURL;
/**
* Creates a new configuration reader.
*
* @param configReader the configuration
*/
public ConfigReader(Reader configReader) {
JsonObject root = new JsonParser().parse(configReader).getAsJsonObject();
this.baseURL = root.has("baseURL") ? root.getAsJsonPrimitive("baseURL").getAsString() : null;
for (Entry<String, JsonElement> entry : root.getAsJsonObject("datasourcetypes").entrySet()) {
final String className = entry.getValue().getAsString();
dataSourceTypes.put(entry.getKey(), initDataSouceType(className) );
}
for (Entry<String, JsonElement> entry : root.getAsJsonObject("datasources").entrySet()) {
JsonObject dataSource = entry.getValue().getAsJsonObject();
this.dataSources.put(entry.getKey(), dataSource);
}
for (Entry<String, JsonElement> entry : root.getAsJsonObject("prefixes").entrySet()) {
this.prefixes.put(entry.getKey(), entry.getValue().getAsString());
}
}
/**
* Gets the data source types.
*
* @return a mapping of names of data source types to these types
*/
public Map<String, IDataSourceType> getDataSourceTypes() {
return dataSourceTypes;
}
/**
* Gets the data sources.
*
* @return the data sources
*/
public Map<String, JsonObject> getDataSources() {
return dataSources;
}
/**
* Gets the prefixes.
*
* @return the prefixes
*/
public Map<String, String> getPrefixes() {
return prefixes;
}
/**
* Gets the base URL
*
* @return the base URL
*/
public String getBaseURL() {
return baseURL;
}
/**
* Loads a certain {@link IDataSourceType} class at runtime
*
* @param className IDataSourceType class
* @return the created IDataSourceType object
*/
protected IDataSourceType initDataSouceType(final String className )
{
final Class<?> c;
try {
c = Class.forName( className );
}
catch ( ClassNotFoundException e ) {
throw new IllegalArgumentException( "Class not found: " + className,
e );
}
final Object o;
try {
o = c.newInstance();
}
catch ( Exception e ) {
throw new IllegalArgumentException(
"Creating an instance of class '" + className + "' " +
"caused a " + e.getClass().getSimpleName() + ": " +
e.getMessage(), e );
}
if ( ! (o instanceof IDataSourceType) )
throw new IllegalArgumentException(
"Class '" + className + "' is not an implementation " +
"of IDataSourceType." );
return (IDataSourceType) o;
}
}

View file

@ -0,0 +1,76 @@
package org.linkeddatafragments.datasource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
/**
* Base class for implementations of {@link IFragmentRequestProcessor}.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
abstract public class AbstractRequestProcessor
implements IFragmentRequestProcessor
{
@Override
public void close() {}
/**
* Create an {@link ILinkedDataFragment} from {@link ILinkedDataFragmentRequest}
*
* @param request
* @return
* @throws IllegalArgumentException
*/
@Override
final public ILinkedDataFragment createRequestedFragment(
final ILinkedDataFragmentRequest request )
throws IllegalArgumentException
{
return getWorker( request ).createRequestedFragment();
}
/**
* Get the {@link Worker} from {@link ILinkedDataFragmentRequest}
*
* @param request
* @return
* @throws IllegalArgumentException
*/
abstract protected Worker getWorker(
final ILinkedDataFragmentRequest request )
throws IllegalArgumentException;
/**
* Processes {@link ILinkedDataFragmentRequest}s
*
*/
abstract static protected class Worker
{
/**
* The {@link ILinkedDataFragmentRequest} to process
*/
public final ILinkedDataFragmentRequest request;
/**
* Create a Worker
*
* @param request
*/
public Worker( final ILinkedDataFragmentRequest request )
{
this.request = request;
}
/**
* Create the requested {@link ILinkedDataFragment}
*
* @return The ILinkedDataFragment
* @throws IllegalArgumentException
*/
abstract public ILinkedDataFragment createRequestedFragment()
throws IllegalArgumentException;
} // end of class Worker
}

View file

@ -0,0 +1,158 @@
package org.linkeddatafragments.datasource;
import org.apache.jena.rdf.model.Model;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import org.linkeddatafragments.fragments.tpf.TriplePatternFragmentImpl;
/**
* Base class for implementations of {@link IFragmentRequestProcessor} that
* process {@link ITriplePatternFragmentRequest}s.
*
* @param <CTT>
* type for representing constants in triple patterns (i.e., URIs and
* literals)
* @param <NVT>
* type for representing named variables in triple patterns
* @param <AVT>
* type for representing anonymous variables in triple patterns (i.e.,
* variables denoted by a blank node)
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public abstract class
AbstractRequestProcessorForTriplePatterns<CTT,NVT,AVT>
extends AbstractRequestProcessor
{
/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
@Override
protected final Worker<CTT,NVT,AVT> getWorker(
final ILinkedDataFragmentRequest request )
throws IllegalArgumentException
{
if ( request instanceof ITriplePatternFragmentRequest<?,?,?>) {
@SuppressWarnings("unchecked")
final ITriplePatternFragmentRequest<CTT,NVT,AVT> tpfRequest =
(ITriplePatternFragmentRequest<CTT,NVT,AVT>) request;
return getTPFSpecificWorker( tpfRequest );
}
else
throw new IllegalArgumentException( request.getClass().getName() );
}
/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
abstract protected Worker<CTT,NVT,AVT> getTPFSpecificWorker(
final ITriplePatternFragmentRequest<CTT,NVT,AVT> request )
throws IllegalArgumentException;
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
abstract static protected class Worker<CTT,NVT,AVT>
extends AbstractRequestProcessor.Worker
{
/**
*
* @param request
*/
public Worker(
final ITriplePatternFragmentRequest<CTT,NVT,AVT> request )
{
super( request );
}
/**
*
* @return
* @throws IllegalArgumentException
*/
@Override
public ILinkedDataFragment createRequestedFragment()
throws IllegalArgumentException
{
final long limit = ILinkedDataFragmentRequest.TRIPLESPERPAGE;
final long offset;
if ( request.isPageRequest() )
offset = limit * ( request.getPageNumber() - 1L );
else
offset = 0L;
@SuppressWarnings("unchecked")
final ITriplePatternFragmentRequest<CTT,NVT,AVT> tpfRequest =
(ITriplePatternFragmentRequest<CTT,NVT,AVT>) request;
return createFragment( tpfRequest.getSubject(),
tpfRequest.getPredicate(),
tpfRequest.getObject(),
offset, limit );
}
/**
*
* @param subj
* @param pred
* @param obj
* @param offset
* @param limit
* @return
* @throws IllegalArgumentException
*/
abstract protected ILinkedDataFragment createFragment(
final ITriplePatternElement<CTT,NVT,AVT> subj,
final ITriplePatternElement<CTT,NVT,AVT> pred,
final ITriplePatternElement<CTT,NVT,AVT> obj,
final long offset,
final long limit )
throws IllegalArgumentException;
/**
*
* @return
*/
protected ITriplePatternFragment createEmptyTriplePatternFragment()
{
return new TriplePatternFragmentImpl( request.getFragmentURL(),
request.getDatasetURL() );
}
/**
*
* @param triples
* @param totalSize
* @param isLastPage
* @return
*/
protected ITriplePatternFragment createTriplePatternFragment(
final Model triples,
final long totalSize,
final boolean isLastPage )
{
return new TriplePatternFragmentImpl( triples,
totalSize,
request.getFragmentURL(),
request.getDatasetURL(),
request.getPageNumber(),
isLastPage );
}
} // end of class Worker
}

View file

@ -0,0 +1,54 @@
package org.linkeddatafragments.datasource;
/**
* The base class for an {@link IDataSource}
*
* @author Miel Vander Sande
* @author Bart Hanssens
*/
public abstract class DataSourceBase implements IDataSource {
/**
* Get the datasource title
*/
protected String title;
/**
* Get the datasource description
*/
protected String description;
/**
* Create a base for a {@link IDataSource}
*
* @param title the datasource title
* @param description the datasource description
*/
public DataSourceBase(String title, String description) {
this.title = title;
this.description = description;
}
/**
* Get the datasource description
*
* @return
*/
@Override
public String getDescription() {
return this.description;
};
/**
* Get the datasource title
*
* @return
*/
@Override
public String getTitle() {
return this.title;
};
@Override
public void close() {}
}

View file

@ -0,0 +1,35 @@
package org.linkeddatafragments.datasource;
import com.google.gson.JsonObject;
import org.linkeddatafragments.exceptions.DataSourceCreationException;
import org.linkeddatafragments.exceptions.UnknownDataSourceTypeException;
/**
*
* @author Miel Vander Sande
* @author Bart Hanssens
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class DataSourceFactory {
/**
* Create a datasource using a JSON config
*
* @param config
* @return datasource interface
* @throws DataSourceCreationException
*/
public static IDataSource create(JsonObject config) throws DataSourceCreationException {
String title = config.getAsJsonPrimitive("title").getAsString();
String description = config.getAsJsonPrimitive("description").getAsString();
String typeName = config.getAsJsonPrimitive("type").getAsString();
JsonObject settings = config.getAsJsonObject("settings");
final IDataSourceType type = DataSourceTypesRegistry.getType(typeName);
if ( type == null )
throw new UnknownDataSourceTypeException(typeName);
return type.createDataSource( title, description, settings );
}
}

View file

@ -0,0 +1,51 @@
package org.linkeddatafragments.datasource;
import java.util.HashMap;
import java.util.Map;
/**
* A registry of {@link IDataSourceType}s.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class DataSourceTypesRegistry
{
private static Map<String, IDataSourceType> registry =
new HashMap<String, IDataSourceType>();
/**
*
* @param typeName
* @return
*/
public static synchronized IDataSourceType getType( final String typeName )
{
return registry.get( typeName );
}
/**
*
* @param typeName
* @return
*/
public static synchronized boolean isRegistered( final String typeName )
{
return registry.containsKey( typeName );
}
/**
*
* @param typeName
* @param type
*/
public static synchronized void register( final String typeName,
final IDataSourceType type )
{
if ( registry.containsKey(typeName) ) {
throw new IllegalArgumentException( "The registry already " +
"contains a type with the name '" + typeName + "'." );
}
registry.put( typeName, type );
}
}

View file

@ -0,0 +1,38 @@
package org.linkeddatafragments.datasource;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import java.io.Closeable;
/**
* A data source of Linked Data Fragments.
*
* @author Ruben Verborgh
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface IDataSource extends Closeable {
/**
*
* @return
*/
public String getTitle();
/**
*
* @return
*/
public String getDescription();
/**
* Returns a data source specific {@link IFragmentRequestParser}.
* @return
*/
IFragmentRequestParser getRequestParser();
/**
* Returns a data source specific {@link IFragmentRequestProcessor}.
* @return
*/
IFragmentRequestProcessor getRequestProcessor();
}

View file

@ -0,0 +1,33 @@
package org.linkeddatafragments.datasource;
import com.google.gson.JsonObject;
import org.linkeddatafragments.exceptions.DataSourceCreationException;
/**
* Represents types of {@link IDataSource}s that can be used to provide some
* Linked Data Fragments interface.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface IDataSourceType
{
/**
* Creates a data source of this type.
*
* @param title
* The title of the data source (as given in the config file).
*
* @param description
* The description of the data source (as given in the config file).
*
* @param settings
* The properties of the data source to be created; usually, these
* properties are given in the config file of the LDF server.
* @return
* @throws org.linkeddatafragments.exceptions.DataSourceCreationException
*/
IDataSource createDataSource(final String title,
final String description,
final JsonObject settings)
throws DataSourceCreationException;
}

View file

@ -0,0 +1,26 @@
package org.linkeddatafragments.datasource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import java.io.Closeable;
/**
* Processes {@link ILinkedDataFragmentRequest}s and returns
* the requested {@link ILinkedDataFragment}s.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface IFragmentRequestProcessor extends Closeable
{
/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
ILinkedDataFragment createRequestedFragment(
final ILinkedDataFragmentRequest request)
throws IllegalArgumentException;
}

View file

@ -0,0 +1,47 @@
package org.linkeddatafragments.datasource.index;
import org.linkeddatafragments.datasource.DataSourceBase;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;
import java.util.HashMap;
/**
* An Index data source provides an overview of all available datasets.
*
* @author Miel Vander Sande
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class IndexDataSource extends DataSourceBase {
/**
* The request processor
*
*/
protected final IndexRequestProcessorForTPFs requestProcessor;
/**
*
* @param baseUrl
* @param datasources
*/
public IndexDataSource(String baseUrl, HashMap<String, IDataSource> datasources) {
super("Index", "List of all datasources");
requestProcessor = new IndexRequestProcessorForTPFs( baseUrl, datasources );
}
@Override
public IFragmentRequestParser getRequestParser()
{
return TPFRequestParserForJenaBackends.getInstance();
}
@Override
public IFragmentRequestProcessor getRequestProcessor()
{
return requestProcessor;
}
}

View file

@ -0,0 +1,146 @@
package org.linkeddatafragments.datasource.index;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.rdf.model.impl.PropertyImpl;
import org.apache.jena.rdf.model.impl.ResourceImpl;
import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Implementation of {@link IFragmentRequestProcessor} that processes
* {@link ITriplePatternFragmentRequest}s over an index that provides
* an overview of all available datasets.
*
* @author Miel Vander Sande
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class IndexRequestProcessorForTPFs
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
{
final static String RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
final static String RDFS = "http://www.w3.org/2000/01/rdf-schema#";
final static String DC = "http://purl.org/dc/terms/";
final static String VOID = "http://rdfs.org/ns/void#";
private final Model model;
/**
*
* @param baseUrl
* @param datasources
*/
public IndexRequestProcessorForTPFs(
final String baseUrl,
final HashMap<String, IDataSource> datasources )
{
this.model = ModelFactory.createDefaultModel();
for (Map.Entry<String, IDataSource> entry : datasources.entrySet()) {
String datasourceName = entry.getKey();
IDataSource datasource = entry.getValue();
Resource datasourceUrl = new ResourceImpl(baseUrl + "/" + datasourceName);
model.add(datasourceUrl, new PropertyImpl(RDF + "type"), VOID + "Dataset");
model.add(datasourceUrl, new PropertyImpl(RDFS + "label"), datasource.getTitle());
model.add(datasourceUrl, new PropertyImpl(DC + "title"), datasource.getTitle());
model.add(datasourceUrl, new PropertyImpl(DC + "description"), datasource.getDescription());
}
}
/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
@Override
protected Worker getTPFSpecificWorker(
final ITriplePatternFragmentRequest<RDFNode,String,String> request )
throws IllegalArgumentException
{
return new Worker( request );
}
/**
* Worker for the index
*/
protected class Worker
extends AbstractRequestProcessorForTriplePatterns.Worker<RDFNode,String,String>
{
/**
* Creates a Worker for the datasource index
*
* @param req
*/
public Worker(
final ITriplePatternFragmentRequest<RDFNode,String,String> req )
{
super( req );
}
/**
*
* @param s
* @param p
* @param o
* @param offset
* @param limit
* @return
*/
@Override
protected ILinkedDataFragment createFragment(
final ITriplePatternElement<RDFNode,String,String> s,
final ITriplePatternElement<RDFNode,String,String> p,
final ITriplePatternElement<RDFNode,String,String> o,
final long offset,
final long limit )
{
// FIXME: The following algorithm is incorrect for cases in which
// the requested triple pattern contains a specific variable
// multiple times;
// e.g., (?x foaf:knows ?x ) or (_:bn foaf:knows _:bn)
// see https://github.com/LinkedDataFragments/Server.Java/issues/25
final Resource subject = s.isVariable() ? null
: s.asConstantTerm().asResource();
final Property predicate = p.isVariable() ? null
: ResourceFactory.createProperty(p.asConstantTerm().asResource().getURI());
final RDFNode object = o.isVariable() ? null
: o.asConstantTerm();
StmtIterator listStatements = model.listStatements(subject, predicate, object);
Model result = ModelFactory.createDefaultModel();
long index = 0;
while (listStatements.hasNext() && index < offset) {
listStatements.next();
index++;
}
while (listStatements.hasNext() && index < (offset + limit)) {
result.add(listStatements.next());
}
final boolean isLastPage = ( result.size() < offset + limit );
return createTriplePatternFragment( result, result.size(), isLastPage );
}
} // end of class Worker
}

View file

@ -0,0 +1,165 @@
package org.linkeddatafragments.datasource.tdb;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.QuerySolutionMap;
import org.apache.jena.query.ResultSet;
import org.apache.jena.query.Syntax;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.tdb.TDBFactory;
import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import java.io.File;
/**
* Implementation of {@link IFragmentRequestProcessor} that processes
* {@link ITriplePatternFragmentRequest}s over data stored in Jena TDB.
*
* @author <a href="mailto:bart.hanssens@fedict.be">Bart Hanssens</a>
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class JenaTDBBasedRequestProcessorForTPFs
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
{
private final Dataset tdb;
private final String sparql = "CONSTRUCT WHERE { ?s ?p ?o } " +
"ORDER BY ?s ?p ?o";
private final String count = "SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o }";
private final Query query = QueryFactory.create(sparql, Syntax.syntaxSPARQL_11);
private final Query countQuery = QueryFactory.create(count, Syntax.syntaxSPARQL_11);
/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
@Override
protected Worker getTPFSpecificWorker(
final ITriplePatternFragmentRequest<RDFNode,String,String> request )
throws IllegalArgumentException
{
return new Worker( request );
}
/**
*
*/
protected class Worker
extends AbstractRequestProcessorForTriplePatterns.Worker<RDFNode,String,String>
{
/**
*
* @param req
*/
public Worker(
final ITriplePatternFragmentRequest<RDFNode,String,String> req )
{
super( req );
}
/**
*
* @param subject
* @param predicate
* @param object
* @param offset
* @param limit
* @return
*/
@Override
protected ILinkedDataFragment createFragment(
final ITriplePatternElement<RDFNode,String,String> subject,
final ITriplePatternElement<RDFNode,String,String> predicate,
final ITriplePatternElement<RDFNode,String,String> object,
final long offset,
final long limit )
{
// FIXME: The following algorithm is incorrect for cases in which
// the requested triple pattern contains a specific variable
// multiple times;
// e.g., (?x foaf:knows ?x ) or (_:bn foaf:knows _:bn)
// see https://github.com/LinkedDataFragments/Server.Java/issues/24
Model model = tdb.getDefaultModel();
QuerySolutionMap map = new QuerySolutionMap();
if ( ! subject.isVariable() ) {
map.add("s", subject.asConstantTerm());
}
if ( ! predicate.isVariable() ) {
map.add("p", predicate.asConstantTerm());
}
if ( ! object.isVariable() ) {
map.add("o", object.asConstantTerm());
}
query.setOffset(offset);
query.setLimit(limit);
Model triples = ModelFactory.createDefaultModel();
try (QueryExecution qexec = QueryExecutionFactory.create(query, model, map)) {
qexec.execConstruct(triples);
}
if (triples.isEmpty()) {
return createEmptyTriplePatternFragment();
}
// Try to get an estimate
long size = triples.size();
long estimate = -1;
try (QueryExecution qexec = QueryExecutionFactory.create(countQuery, model, map)) {
ResultSet results = qexec.execSelect();
if (results.hasNext()) {
QuerySolution soln = results.nextSolution() ;
Literal literal = soln.getLiteral("count");
estimate = literal.getLong();
}
}
/*GraphStatisticsHandler stats = model.getGraph().getStatisticsHandler();
if (stats != null) {
Node s = (subject != null) ? subject.asNode() : null;
Node p = (predicate != null) ? predicate.asNode() : null;
Node o = (object != null) ? object.asNode() : null;
estimate = stats.getStatistic(s, p, o);
}*/
// No estimate or incorrect
if (estimate < offset + size) {
estimate = (size == limit) ? offset + size + 1 : offset + size;
}
// create the fragment
final boolean isLastPage = ( estimate < offset + limit );
return createTriplePatternFragment( triples, estimate, isLastPage );
}
} // end of class Worker
/**
* Constructor
*
* @param tdbdir directory used for TDB backing
*/
public JenaTDBBasedRequestProcessorForTPFs(File tdbdir) {
this.tdb = TDBFactory.createDataset(tdbdir.getAbsolutePath());
}
}

View file

@ -0,0 +1,47 @@
package org.linkeddatafragments.datasource.tdb;
import org.linkeddatafragments.datasource.DataSourceBase;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;
import java.io.File;
/**
* Experimental Jena TDB-backed data source of Basic Linked Data Fragments.
*
* @author <a href="mailto:bart.hanssens@fedict.be">Bart Hanssens</a>
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class JenaTDBDataSource extends DataSourceBase {
/**
* The request processor
*
*/
protected final JenaTDBBasedRequestProcessorForTPFs requestProcessor;
@Override
public IFragmentRequestParser getRequestParser()
{
return TPFRequestParserForJenaBackends.getInstance();
}
@Override
public IFragmentRequestProcessor getRequestProcessor()
{
return requestProcessor;
}
/**
* Constructor
*
* @param title
* @param description
* @param tdbdir directory used for TDB backing
*/
public JenaTDBDataSource(String title, String description, File tdbdir) {
super(title, description);
requestProcessor = new JenaTDBBasedRequestProcessorForTPFs( tdbdir );
}
}

View file

@ -0,0 +1,34 @@
package org.linkeddatafragments.datasource.tdb;
import com.google.gson.JsonObject;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IDataSourceType;
import org.linkeddatafragments.exceptions.DataSourceCreationException;
import java.io.File;
/**
* The type of Triple Pattern Fragment data sources that are backed by
* a Jena TDB instance.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class JenaTDBDataSourceType implements IDataSourceType
{
@Override
public IDataSource createDataSource( final String title,
final String description,
final JsonObject settings )
throws DataSourceCreationException
{
final String dname = settings.getAsJsonPrimitive("directory").getAsString();
final File dir = new File( dname );
try {
return new JenaTDBDataSource(title, description, dir);
} catch (Exception ex) {
throw new DataSourceCreationException(ex);
}
}
}

View file

@ -0,0 +1,25 @@
package org.linkeddatafragments.exceptions;
/**
*
* @author Miel Vander Sande
*/
public class DataSourceCreationException extends DataSourceException {
/**
*
* @param cause
*/
public DataSourceCreationException(Throwable cause) {
super(cause);
}
/**
*
* @param datasourceName
* @param message
*/
public DataSourceCreationException(String datasourceName, String message) {
super(datasourceName, "Could not create DataSource - " + message);
}
}

View file

@ -0,0 +1,37 @@
package org.linkeddatafragments.exceptions;
import org.linkeddatafragments.datasource.IDataSource;
/**
*
* @author Miel Vander Sande
*/
abstract public class DataSourceException extends Exception {
/**
*
* @param cause
*/
public DataSourceException(Throwable cause) {
super(cause);
}
/**
*
* @param datasourceName
* @param message
*/
public DataSourceException(String datasourceName, String message) {
super("Error for datasource '" + datasourceName + "': " + message);
}
/**
*
* @param datasource
* @param message
*/
public DataSourceException(IDataSource datasource, String message) {
this(datasource.getTitle(), message);
}
}

View file

@ -0,0 +1,16 @@
package org.linkeddatafragments.exceptions;
/**
*
* @author Miel Vander Sande
*/
public class DataSourceNotFoundException extends DataSourceException {
/**
*
* @param dataSourceName
*/
public DataSourceNotFoundException(String dataSourceName) {
super(dataSourceName, "Datasource not found.");
}
}

View file

@ -0,0 +1,17 @@
package org.linkeddatafragments.exceptions;
/**
* Exception thrown when no mimeTypes are known to the system
*
* @author Miel Vander Sande
*/
public class NoRegisteredMimeTypesException extends Exception {
/**
* Constructs the exception
*/
public NoRegisteredMimeTypesException() {
super("List of supported mimeTypes is empty.");
}
}

View file

@ -0,0 +1,16 @@
package org.linkeddatafragments.exceptions;
/**
*
* @author Miel Vander Sande
*/
public class UnknownDataSourceTypeException extends DataSourceCreationException {
/**
*
* @param type
*/
public UnknownDataSourceTypeException(String type) {
super("", "Type " + type + " does not exist.");
}
}

View file

@ -0,0 +1,141 @@
package org.linkeddatafragments.fragments;
import org.linkeddatafragments.config.ConfigReader;
import javax.servlet.http.HttpServletRequest;
/**
* Base class for implementations of {@link IFragmentRequestParser}.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
abstract public class FragmentRequestParserBase implements IFragmentRequestParser
{
@Override
final public ILinkedDataFragmentRequest parseIntoFragmentRequest(
final HttpServletRequest httpRequest,
final ConfigReader config )
throws IllegalArgumentException
{
return getWorker( httpRequest, config ).createFragmentRequest();
}
/**
*
* @param httpRequest
* @param config
* @return
* @throws IllegalArgumentException
*/
abstract protected Worker getWorker( final HttpServletRequest httpRequest,
final ConfigReader config )
throws IllegalArgumentException;
/**
*
*/
abstract static protected class Worker
{
/**
*
*/
public final HttpServletRequest request;
/**
*
*/
public final ConfigReader config;
/**
*
*/
public final boolean pageNumberWasRequested;
/**
*
*/
public final long pageNumber;
/**
*
* @param request
* @param config
*/
public Worker( final HttpServletRequest request,
final ConfigReader config )
{
this.request = request;
this.config = config;
final String givenPageNumber = request.getParameter(
ILinkedDataFragmentRequest.PARAMETERNAME_PAGE );
if ( givenPageNumber != null ) {
long pageNumber;
try {
pageNumber = Long.parseLong( givenPageNumber );
} catch (NumberFormatException ex) {
pageNumber = 1L;
}
this.pageNumber = ( pageNumber > 0 ) ? pageNumber : 1L;
this.pageNumberWasRequested = true;
}
else {
this.pageNumber = 1L;
this.pageNumberWasRequested = false;
}
}
/**
*
* @return
* @throws IllegalArgumentException
*/
abstract public ILinkedDataFragmentRequest createFragmentRequest()
throws IllegalArgumentException;
/**
*
* @return
*/
public String getFragmentURL() {
final String datasetURL = getDatasetURL();
final String query = request.getQueryString();
return query == null ? datasetURL : (datasetURL + "?" + query);
}
/**
*
* @return
*/
public String getDatasetURL() {
return extractBaseURL( request, config ) + request.getRequestURI();
}
} // end of class Worker
// ----- HELPERS ---------
/**
*
* @param request
* @param config
* @return
*/
public static String extractBaseURL( final HttpServletRequest request,
final ConfigReader config ) {
if (config.getBaseURL() != null) {
return config.getBaseURL();
} else if ((request.getServerPort() == 80)
|| (request.getServerPort() == 443)) {
return request.getScheme() + "://"
+ request.getServerName();
} else {
return request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort();
}
}
}

View file

@ -0,0 +1,29 @@
package org.linkeddatafragments.fragments;
import org.linkeddatafragments.config.ConfigReader;
import javax.servlet.http.HttpServletRequest;
/**
* Parses HTTP requests into specific {@link ILinkedDataFragmentRequest}s.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface IFragmentRequestParser
{
/**
* Parses the given HTTP request into a specific
* {@link ILinkedDataFragmentRequest}.
*
* @param httpRequest
* @param config
* @return
* @throws IllegalArgumentException
* If the given HTTP request cannot be interpreted (perhaps due to
* missing request parameters).
*/
ILinkedDataFragmentRequest parseIntoFragmentRequest(
final HttpServletRequest httpRequest,
final ConfigReader config)
throws IllegalArgumentException;
}

View file

@ -0,0 +1,79 @@
package org.linkeddatafragments.fragments;
import org.apache.jena.rdf.model.StmtIterator;
import java.io.Closeable;
/**
* Represents any possible Linked Data Fragment.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface ILinkedDataFragment extends Closeable
{
/**
* Returns an iterator over the RDF data of this fragment (possibly only
* partial if the data is paged, as indicated by {@link #isPageOnly()}).
* @return
*/
StmtIterator getTriples();
/**
* Returns true if {@link #getTriples()} returns a page of data only.
* In this case, {@link #getPageNumber()} can be used to obtain the
* corresponding page number.
* @return
*/
boolean isPageOnly();
/**
* Returns the number of the page of data returned by {@link #getTriples()}
* if the data is paged (that is, if {@link #isPageOnly()} returns true).
*
* If the data is not paged, this method throws an exception.
*
* @return
* @throws UnsupportedOperationException
* If the data of this fragment is not paged.
*/
long getPageNumber() throws UnsupportedOperationException;
/**
* Returns true if {@link #getTriples()} returns a page of data only and
* this is the last page of the fragment.
*
* If the data is not paged (i.e., if {@link #isPageOnly()} returns false),
* this method throws an exception.
*
* @return
* @throws UnsupportedOperationException
* If the data of this fragment is not paged.
*/
boolean isLastPage() throws UnsupportedOperationException;
/**
* Returns the maximum number of triples per page if {@link #getTriples()}
* returns a page of data only (that is, if {@link #isPageOnly()} returns
* true).
*
* If the data is not paged, this method throws an exception.
*
* @return
* @throws UnsupportedOperationException
* If the data of this fragment is not paged.
*/
long getMaxPageSize() throws UnsupportedOperationException;
/**
* Returns an iterator over the metadata of this fragment.
* @return
*/
StmtIterator getMetadata();
/**
* Returns an iterator over an RDF description of the controls associated
* with this fragment.
* @return
*/
StmtIterator getControls();
}

View file

@ -0,0 +1,48 @@
package org.linkeddatafragments.fragments;
/**
* Basis for representing a request of some type of Linked Data Fragment (LDF).
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface ILinkedDataFragmentRequest
{
/**
*
*/
public final static long TRIPLESPERPAGE = 100L;
/**
*
*/
public final static String PARAMETERNAME_PAGE = "page";
/**
* Returns the URL of the requested LDF.
* @return
*/
String getFragmentURL();
/**
* Returns the URL of the dataset to which the requested LDF belongs.
* @return
*/
String getDatasetURL();
/**
* Returns true if the request is for a specific page of the requested
* fragment. In this case, {@link #getPageNumber()} can be used to obtain
* the requested page number.
* @return
*/
boolean isPageRequest();
/**
* Returns the number of the page requested for the LDF; if this is not a
* page-based request (that is, if {@link #isPageRequest()} returns true),
* then this method returns 1.
* @return
*/
long getPageNumber();
}

View file

@ -0,0 +1,189 @@
package org.linkeddatafragments.fragments;
import org.apache.http.client.utils.URIBuilder;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.StmtIterator;
import org.linkeddatafragments.util.CommonResources;
import java.net.URISyntaxException;
/**
* Base class of any implementation of {@link ILinkedDataFragment} that uses
* paging.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public abstract class LinkedDataFragmentBase implements ILinkedDataFragment
{
/**
*
*/
public final String fragmentURL;
/**
*
*/
public final String datasetURL;
/**
*
*/
public final long pageNumber;
/**
*
*/
public final boolean isLastPage;
/**
*
* @param fragmentURL
* @param datasetURL
* @param pageNumber
* @param isLastPage
*/
protected LinkedDataFragmentBase( final String fragmentURL,
final String datasetURL,
final long pageNumber,
final boolean isLastPage )
{
this.fragmentURL = fragmentURL;
this.datasetURL = datasetURL;
this.pageNumber = pageNumber;
this.isLastPage = isLastPage;
}
/**
* Does nothing. May be overridden by subclasses that hold some objects
* that need to be closed (such as iterators from the underlying data
* source).
*/
@Override
public void close() {}
@Override
public boolean isPageOnly() {
return true;
}
@Override
public long getPageNumber() {
return pageNumber;
}
@Override
public boolean isLastPage() {
return isLastPage;
}
@Override
public long getMaxPageSize() {
return ILinkedDataFragmentRequest.TRIPLESPERPAGE;
}
/**
* This implementation uses {@link #addMetadata(Model)}, which should be
* overridden in subclasses (instead of overriding this method).
* @return
*/
@Override
public StmtIterator getMetadata()
{
final Model output = ModelFactory.createDefaultModel();
addMetadata( output );
return output.listStatements();
}
/**
* This implementation uses {@link #addControls(Model)}, which should be
* overridden in subclasses (instead of overriding this method).
* @return
*/
@Override
public StmtIterator getControls()
{
final Model output = ModelFactory.createDefaultModel();
addControls( output );
return output.listStatements();
}
/**
* Adds some basic metadata to the given RDF model.
* This method may be overridden in subclasses.
* @param model
*/
public void addMetadata( final Model model )
{
final Resource datasetId = model.createResource( getDatasetURI() );
final Resource fragmentId = model.createResource( fragmentURL );
datasetId.addProperty( CommonResources.RDF_TYPE, CommonResources.VOID_DATASET );
datasetId.addProperty( CommonResources.RDF_TYPE, CommonResources.HYDRA_COLLECTION );
datasetId.addProperty( CommonResources.VOID_SUBSET, fragmentId );
Literal itemsPerPage = model.createTypedLiteral(this.getMaxPageSize());
datasetId.addProperty( CommonResources.HYDRA_ITEMSPERPAGE, itemsPerPage);
fragmentId.addProperty( CommonResources.RDF_TYPE, CommonResources.HYDRA_COLLECTION );
fragmentId.addProperty( CommonResources.RDF_TYPE, CommonResources.HYDRA_PAGEDCOLLECTION );
}
/**
* Adds an RDF description of page links to the given RDF model.
* This method may be overridden in subclasses.
* @param model
*/
public void addControls( final Model model )
{
final URIBuilder pagedURL;
try {
pagedURL = new URIBuilder( fragmentURL );
}
catch ( URISyntaxException e ) {
throw new IllegalArgumentException( e );
}
final Resource fragmentId = model.createResource( fragmentURL );
final Resource firstPageId =
model.createResource(
pagedURL.setParameter(ILinkedDataFragmentRequest.PARAMETERNAME_PAGE,
"1").toString() );
fragmentId.addProperty( CommonResources.HYDRA_FIRSTPAGE, firstPageId );
if ( pageNumber > 1) {
final String prevPageNumber = Long.toString( pageNumber - 1 );
final Resource prevPageId =
model.createResource(
pagedURL.setParameter(ILinkedDataFragmentRequest.PARAMETERNAME_PAGE,
prevPageNumber).toString() );
fragmentId.addProperty( CommonResources.HYDRA_PREVIOUSPAGE, prevPageId );
}
if ( ! isLastPage ) {
final String nextPageNumber = Long.toString( pageNumber + 1 );
final Resource nextPageId =
model.createResource(
pagedURL.setParameter(ILinkedDataFragmentRequest.PARAMETERNAME_PAGE,
nextPageNumber).toString() );
fragmentId.addProperty( CommonResources.HYDRA_NEXTPAGE, nextPageId );
}
}
/**
*
* @return
*/
public String getDatasetURI() {
return datasetURL + "#dataset";
}
}

View file

@ -0,0 +1,81 @@
package org.linkeddatafragments.fragments;
/**
* Base class for implementations of {@link ILinkedDataFragmentRequest}.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public abstract class LinkedDataFragmentRequestBase
implements ILinkedDataFragmentRequest
{
/**
*
*/
public final String fragmentURL;
/**
*
*/
public final String datasetURL;
/**
*
*/
public final boolean pageNumberWasRequested;
/**
*
*/
public final long pageNumber;
/**
*
* @param fragmentURL
* @param datasetURL
* @param pageNumberWasRequested
* @param pageNumber
*/
public LinkedDataFragmentRequestBase( final String fragmentURL,
final String datasetURL,
final boolean pageNumberWasRequested,
final long pageNumber )
{
this.fragmentURL = fragmentURL;
this.datasetURL = datasetURL;
this.pageNumberWasRequested = pageNumberWasRequested;
this.pageNumber = (pageNumberWasRequested) ? pageNumber : 1L;
}
@Override
public String getFragmentURL() {
return fragmentURL;
}
@Override
public String getDatasetURL() {
return datasetURL;
}
@Override
public boolean isPageRequest() {
return pageNumberWasRequested;
}
@Override
public long getPageNumber() {
return pageNumber;
}
@Override
public String toString()
{
return "LinkedDataFragmentRequest(" +
"class: " + getClass().getName() +
", fragmentURL: " + fragmentURL +
", isPageRequest: " + pageNumberWasRequested +
", pageNumber: " + pageNumber +
")";
}
}

View file

@ -0,0 +1,89 @@
package org.linkeddatafragments.fragments.tpf;
/**
* Represents an element of a triple pattern (i.e., subject, predicate, object).
*
* @param <ConstantTermType> type for representing constants in triple patterns
* (i.e., URIs and literals)
* @param <NamedVarType> type for representing named variables in triple patterns
* @param <AnonVarType> type for representing anonymous variables in triple
* patterns (i.e., variables denoted by a blank node)
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType>
{
/**
* Returns true if this element is a variable (specific or unspecified).
* @return
*/
boolean isVariable();
/**
* Returns true if this element is a specific variable, and false if either
* it is not a variable (but a URI or literal) or it is some variable that
* is not specified. The latter (unspecified variables) is possible because
* when a client requests a triple pattern fragment, it may omit triple
* pattern related parameters.
* @return
*/
boolean isSpecificVariable();
/**
* Returns true if this element is a specific variable that has a name
* (i.e., it is denoted by a string that begins with a question mark),
* and false if either it is not a specific variable or it is a specific
* variable that is denoted by a blank node.
*
* If this element is a specific variable that has a name (that is, this
* method returns true), the named variable can be obtained by the method
* {@link #asNamedVariable()}.
* @return
*/
boolean isNamedVariable();
/**
* Returns a representation of this element as a named variable (assuming
* it is a specific variable that has a name).
*
* @return
* @throws UnsupportedOperationException
* If this element is not a specific variable that has a name
* (i.e., if {@link #isNamedVariable()} returns false).
*/
NamedVarType asNamedVariable() throws UnsupportedOperationException;
/**
* Returns true if this element is a specific variable that does not have
* a name (i.e., it is denoted by a blank node), and false if either it is
* not a specific variable or it is a specific variable that has a name.
*
* If this element is a specific variable denoted by a blank node (that is,
* this method returns true), the blank node can be obtained by the method
* {@link #asAnonymousVariable()}.
* @return
*/
boolean isAnonymousVariable();
/**
* Returns a representation of this element as a blank node (assuming
* it is a specific, but non-named variable).
*
* @return
* @throws UnsupportedOperationException
* If this element is not a specific anonymous variable (i.e.,
* if {@link #isAnonymousVariable()} returns false).
*/
AnonVarType asAnonymousVariable() throws UnsupportedOperationException;
/**
* Returns a representation of this element as a constant RDF term (i.e.,
* a URI or a literal).
*
* @return
* @throws UnsupportedOperationException
* If this element is not a constant RDF term but a variable
* (i.e., if {@link #isVariable()} returns true).
*/
ConstantTermType asConstantTerm() throws UnsupportedOperationException;
}

View file

@ -0,0 +1,15 @@
package org.linkeddatafragments.fragments.tpf;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
/**
* A Triple Pattern Fragment.
* @author Ruben Verborgh
*/
public interface ITriplePatternFragment extends ILinkedDataFragment {
/**
* Gets the total number of triples in the fragment (can be an estimate).
* @return the total number of triples
*/
public long getTotalSize();
}

View file

@ -0,0 +1,52 @@
package org.linkeddatafragments.fragments.tpf;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
/**
* Represents a request of a Triple Pattern Fragment (TPF).
*
* @param <ConstantTermType> type for representing constants in triple patterns
* (i.e., URIs and literals)
* @param <NamedVarType> type for representing named variables in triple patterns
* @param <AnonVarType> type for representing anonymous variables in triple
* patterns (i.e., variables denoted by a blank node)
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public interface ITriplePatternFragmentRequest<ConstantTermType,NamedVarType,AnonVarType>
extends ILinkedDataFragmentRequest
{
/**
*
*/
public final static String PARAMETERNAME_SUBJ = "subject";
/**
*
*/
public final static String PARAMETERNAME_PRED = "predicate";
/**
*
*/
public final static String PARAMETERNAME_OBJ = "object";
/**
* Returns the subject position of the requested triple pattern.
* @return
*/
ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getSubject();
/**
* Returns the predicate position of the requested triple pattern.
* @return
*/
ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getPredicate();
/**
* Returns the object position of the requested triple pattern.
* @return
*/
ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getObject();
}

View file

@ -0,0 +1,127 @@
package org.linkeddatafragments.fragments.tpf;
import org.linkeddatafragments.config.ConfigReader;
import org.linkeddatafragments.fragments.FragmentRequestParserBase;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.util.TriplePatternElementParser;
import javax.servlet.http.HttpServletRequest;
/**
* An {@link IFragmentRequestParser} for {@link ITriplePatternFragmentRequest}s.
*
* @param <ConstantTermType>
* @param <NamedVarType>
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
* @param <AnonVarType>
*/
public class TPFRequestParser<ConstantTermType,NamedVarType,AnonVarType>
extends FragmentRequestParserBase
{
public final TriplePatternElementParser<ConstantTermType,NamedVarType,AnonVarType> elmtParser;
/**
*
* @param elmtParser
*/
public TPFRequestParser(
final TriplePatternElementParser<ConstantTermType,NamedVarType,AnonVarType> elmtParser )
{
this.elmtParser = elmtParser;
}
/**
*
* @param httpRequest
* @param config
* @return
* @throws IllegalArgumentException
*/
@Override
protected Worker getWorker( final HttpServletRequest httpRequest,
final ConfigReader config )
throws IllegalArgumentException
{
return new Worker( httpRequest, config );
}
/**
*
*/
protected class Worker extends FragmentRequestParserBase.Worker
{
/**
*
* @param request
* @param config
*/
public Worker( final HttpServletRequest request,
final ConfigReader config )
{
super( request, config );
}
/**
*
* @return
* @throws IllegalArgumentException
*/
@Override
public ILinkedDataFragmentRequest createFragmentRequest()
throws IllegalArgumentException
{
return new TriplePatternFragmentRequestImpl<ConstantTermType,NamedVarType,AnonVarType>(
getFragmentURL(),
getDatasetURL(),
pageNumberWasRequested,
pageNumber,
getSubject(),
getPredicate(),
getObject() );
}
/**
*
* @return
*/
public ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getSubject() {
return getParameterAsTriplePatternElement(
ITriplePatternFragmentRequest.PARAMETERNAME_SUBJ );
}
/**
*
* @return
*/
public ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getPredicate() {
return getParameterAsTriplePatternElement(
ITriplePatternFragmentRequest.PARAMETERNAME_PRED );
}
/**
*
* @return
*/
public ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType> getObject() {
return getParameterAsTriplePatternElement(
ITriplePatternFragmentRequest.PARAMETERNAME_OBJ );
}
/**
*
* @param paramName
* @return
*/
public ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType>
getParameterAsTriplePatternElement( final String paramName )
{
final String parameter = request.getParameter( paramName );
return elmtParser.parseIntoTriplePatternElement( parameter );
}
} // end of class Worker
}

View file

@ -0,0 +1,35 @@
package org.linkeddatafragments.fragments.tpf;
import org.apache.jena.rdf.model.RDFNode;
import org.linkeddatafragments.util.TriplePatternElementParserForJena;
/**
* An {@link TPFRequestParser} for Jena-based backends.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class TPFRequestParserForJenaBackends
extends TPFRequestParser<RDFNode,String,String>
{
private static TPFRequestParserForJenaBackends instance = null;
/**
*
* @return
*/
public static TPFRequestParserForJenaBackends getInstance()
{
if ( instance == null ) {
instance = new TPFRequestParserForJenaBackends();
}
return instance;
}
/**
*
*/
protected TPFRequestParserForJenaBackends()
{
super( TriplePatternElementParserForJena.getInstance() );
}
}

View file

@ -0,0 +1,214 @@
package org.linkeddatafragments.fragments.tpf;
/**
* A factory for {@link ITriplePatternElement}s.
*
* @param <CTT>
* type for representing constants in triple patterns (i.e., URIs and
* literals)
* @param <NVT>
* type for representing named variables in triple patterns
* @param <AVT>
* type for representing anonymous variables in triple patterns (i.e.,
* variables denoted by a blank node)
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class TriplePatternElementFactory<CTT,NVT,AVT>
{
/**
*
* @return
*/
public ITriplePatternElement<CTT,NVT,AVT> createUnspecifiedVariable()
{
return new UnspecifiedVariable<CTT,NVT,AVT>();
}
/**
*
* @param v
* @return
*/
public ITriplePatternElement<CTT,NVT,AVT> createNamedVariable(final NVT v )
{
return new NamedVariable<CTT,NVT,AVT>( v );
}
/**
*
* @param bnode
* @return
*/
public ITriplePatternElement<CTT,NVT,AVT> createAnonymousVariable(
final AVT bnode )
{
return new AnonymousVariable<CTT,NVT,AVT>( bnode );
}
/**
*
* @param term
* @return
*/
public ITriplePatternElement<CTT,NVT,AVT> createConstantRDFTerm(
final CTT term )
{
return new ConstantRDFTerm<CTT,NVT,AVT>( term );
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static abstract public class Variable<CTT,NVT,AVT>
implements ITriplePatternElement<CTT,NVT,AVT>
{
@Override
public boolean isVariable() { return true; }
@Override
public CTT asConstantTerm() { throw new UnsupportedOperationException(); }
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static public class UnspecifiedVariable<CTT,NVT,AVT>
extends Variable<CTT,NVT,AVT>
{
@Override
public boolean isSpecificVariable() { return false; }
@Override
public boolean isNamedVariable() { return false; }
@Override
public NVT asNamedVariable() { throw new UnsupportedOperationException(); }
@Override
public boolean isAnonymousVariable() { return false; }
@Override
public AVT asAnonymousVariable() { throw new UnsupportedOperationException(); }
@Override
public String toString() { return "UnspecifiedVariable"; }
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static abstract public class SpecificVariable<CTT,NVT,AVT>
extends Variable<CTT,NVT,AVT>
{
@Override
public boolean isSpecificVariable() { return true; }
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static public class NamedVariable<CTT,NVT,AVT>
extends SpecificVariable<CTT,NVT,AVT>
{
/**
*
*/
protected final NVT v;
/**
*
* @param variable
*/
public NamedVariable( final NVT variable ) { v = variable; }
@Override
public boolean isNamedVariable() { return true; }
@Override
public NVT asNamedVariable() { return v; }
@Override
public boolean isAnonymousVariable() { return false; }
@Override
public AVT asAnonymousVariable() { throw new UnsupportedOperationException(); }
@Override
public String toString() { return "NamedVariable(" + v.toString() + ")"; }
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static public class AnonymousVariable<CTT,NVT,AVT>
extends SpecificVariable<CTT,NVT,AVT>
{
/**
*
*/
protected final AVT bn;
/**
*
* @param bnode
*/
public AnonymousVariable( final AVT bnode ) { bn = bnode; }
@Override
public boolean isNamedVariable() { return false; }
@Override
public NVT asNamedVariable() { throw new UnsupportedOperationException(); }
@Override
public boolean isAnonymousVariable() { return true; }
@Override
public AVT asAnonymousVariable() { return bn; }
@Override
public String toString() { return "AnonymousVariable(" + bn.toString() + ")"; }
}
/**
*
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
static public class ConstantRDFTerm<CTT,NVT,AVT>
implements ITriplePatternElement<CTT,NVT,AVT>
{
/**
*
*/
protected final CTT t;
/**
*
* @param term
*/
public ConstantRDFTerm( final CTT term ) { t = term; }
@Override
public boolean isVariable() { return false; }
@Override
public boolean isSpecificVariable() { return false; }
@Override
public boolean isNamedVariable() { return false; }
@Override
public NVT asNamedVariable() { throw new UnsupportedOperationException(); }
@Override
public boolean isAnonymousVariable() { return false; }
@Override
public AVT asAnonymousVariable() { throw new UnsupportedOperationException(); }
@Override
public CTT asConstantTerm() { return t; }
@Override
public String toString() { return "ConstantRDFTerm(" + t.toString() + ")(type: " + t.getClass().getSimpleName() + ")"; }
}
}

View file

@ -0,0 +1,164 @@
package org.linkeddatafragments.fragments.tpf;
import org.apache.jena.datatypes.xsd.XSDDatatype;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.util.iterator.NiceIterator;
import org.linkeddatafragments.fragments.LinkedDataFragmentBase;
import org.linkeddatafragments.util.CommonResources;
import java.util.NoSuchElementException;
/**
* Base class for implementations of {@link ITriplePatternFragment}.
*
* @author Ruben Verborgh
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
abstract public class TriplePatternFragmentBase extends LinkedDataFragmentBase
implements ITriplePatternFragment
{
private final long totalSize;
/**
* Creates an empty Triple Pattern Fragment.
* @param fragmentURL
* @param datasetURL
*/
public TriplePatternFragmentBase( final String fragmentURL,
final String datasetURL ) {
this( 0L, fragmentURL, datasetURL, 1, true );
}
/**
* Creates an empty Triple Pattern Fragment page.
* @param fragmentURL
* @param isLastPage
* @param datasetURL
* @param pageNumber
*/
public TriplePatternFragmentBase( final String fragmentURL,
final String datasetURL,
final long pageNumber,
final boolean isLastPage ) {
this( 0L, fragmentURL, datasetURL, pageNumber, isLastPage );
}
/**
* Creates a new Triple Pattern Fragment.
* @param totalSize the total size
* @param fragmentURL
* @param datasetURL
* @param pageNumber
* @param isLastPage
*/
public TriplePatternFragmentBase( long totalSize,
final String fragmentURL,
final String datasetURL,
final long pageNumber,
final boolean isLastPage ) {
super( fragmentURL, datasetURL, pageNumber, isLastPage );
this.totalSize = totalSize < 0L ? 0L : totalSize;
}
@Override
public StmtIterator getTriples() {
if ( totalSize == 0L )
return emptyStmtIterator;
else
return getNonEmptyStmtIterator();
}
/**
*
* @return
*/
abstract protected StmtIterator getNonEmptyStmtIterator();
@Override
public long getTotalSize() {
return totalSize;
}
@Override
public void addMetadata( final Model model )
{
super.addMetadata( model );
final Resource fragmentId = model.createResource( fragmentURL );
final Literal totalTyped = model.createTypedLiteral( totalSize,
XSDDatatype.XSDinteger );
final Literal limitTyped = model.createTypedLiteral( getMaxPageSize(),
XSDDatatype.XSDinteger );
fragmentId.addLiteral( CommonResources.VOID_TRIPLES, totalTyped );
fragmentId.addLiteral( CommonResources.HYDRA_TOTALITEMS, totalTyped );
fragmentId.addLiteral( CommonResources.HYDRA_ITEMSPERPAGE, limitTyped );
}
@Override
public void addControls( final Model model )
{
super.addControls( model );
final Resource datasetId = model.createResource( getDatasetURI() );
final Resource triplePattern = model.createResource();
final Resource subjectMapping = model.createResource();
final Resource predicateMapping = model.createResource();
final Resource objectMapping = model.createResource();
datasetId.addProperty( CommonResources.HYDRA_SEARCH, triplePattern );
triplePattern.addProperty( CommonResources.HYDRA_TEMPLATE, getTemplate() );
triplePattern.addProperty( CommonResources.HYDRA_MAPPING, subjectMapping );
triplePattern.addProperty( CommonResources.HYDRA_MAPPING, predicateMapping );
triplePattern.addProperty( CommonResources.HYDRA_MAPPING, objectMapping );
subjectMapping.addProperty( CommonResources.HYDRA_VARIABLE, ITriplePatternFragmentRequest.PARAMETERNAME_SUBJ );
subjectMapping.addProperty( CommonResources.HYDRA_PROPERTY, CommonResources.RDF_SUBJECT );
predicateMapping.addProperty( CommonResources.HYDRA_VARIABLE, ITriplePatternFragmentRequest.PARAMETERNAME_PRED );
predicateMapping.addProperty( CommonResources.HYDRA_PROPERTY, CommonResources.RDF_PREDICATE );
objectMapping.addProperty( CommonResources.HYDRA_VARIABLE, ITriplePatternFragmentRequest.PARAMETERNAME_OBJ );
objectMapping.addProperty( CommonResources.HYDRA_PROPERTY, CommonResources.RDF_OBJECT );
}
/**
*
* @return
*/
public String getTemplate() {
return datasetURL + "{?" +
ITriplePatternFragmentRequest.PARAMETERNAME_SUBJ + "," +
ITriplePatternFragmentRequest.PARAMETERNAME_PRED + "," +
ITriplePatternFragmentRequest.PARAMETERNAME_OBJ + "}";
}
/**
*
*/
public static final StmtIterator emptyStmtIterator = new EmptyStmtIterator();
/**
*
*/
public static class EmptyStmtIterator
extends NiceIterator<Statement>
implements StmtIterator
{
/**
*
* @return
*/
public Statement nextStatement() { throw new NoSuchElementException(); }
}
}

View file

@ -0,0 +1,72 @@
package org.linkeddatafragments.fragments.tpf;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.StmtIterator;
/**
* Implementation of {@link ITriplePatternFragment}.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class TriplePatternFragmentImpl extends TriplePatternFragmentBase
{
/**
*
*/
protected final Model triples;
/**
* Creates an empty Triple Pattern Fragment.
* @param fragmentURL
* @param datasetURL
*/
public TriplePatternFragmentImpl( final String fragmentURL,
final String datasetURL ) {
this( null, 0L, fragmentURL, datasetURL, 1, true );
}
/**
* Creates an empty Triple Pattern Fragment page.
* @param fragmentURL
* @param datasetURL
* @param isLastPage
* @param pageNumber
*/
public TriplePatternFragmentImpl( final String fragmentURL,
final String datasetURL,
final long pageNumber,
final boolean isLastPage ) {
this( null, 0L, fragmentURL, datasetURL, pageNumber, isLastPage );
}
/**
* Creates a new Triple Pattern Fragment.
* @param triples the triples (possibly partial)
* @param totalSize the total size
* @param fragmentURL
* @param datasetURL
* @param isLastPage
* @param pageNumber
*/
public TriplePatternFragmentImpl( final Model triples,
long totalSize,
final String fragmentURL,
final String datasetURL,
final long pageNumber,
final boolean isLastPage ) {
super( totalSize, fragmentURL, datasetURL, pageNumber, isLastPage );
this.triples = triples;
}
/**
*
* @return
*/
@Override
protected StmtIterator getNonEmptyStmtIterator() {
return triples.listStatements();
}
}

View file

@ -0,0 +1,96 @@
package org.linkeddatafragments.fragments.tpf;
import org.linkeddatafragments.fragments.LinkedDataFragmentRequestBase;
/**
* An implementation of {@link ITriplePatternFragmentRequest}.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
* @param <CTT>
* @param <NVT>
* @param <AVT>
*/
public class TriplePatternFragmentRequestImpl<CTT,NVT,AVT>
extends LinkedDataFragmentRequestBase
implements ITriplePatternFragmentRequest<CTT,NVT,AVT>
{
/**
*
*/
public final ITriplePatternElement<CTT,NVT,AVT> subject;
/**
*
*/
public final ITriplePatternElement<CTT,NVT,AVT> predicate;
/**
*
*/
public final ITriplePatternElement<CTT,NVT,AVT> object;
/**
*
* @param fragmentURL
* @param datasetURL
* @param pageNumberWasRequested
* @param pageNumber
* @param subject
* @param predicate
* @param object
*/
public TriplePatternFragmentRequestImpl( final String fragmentURL,
final String datasetURL,
final boolean pageNumberWasRequested,
final long pageNumber,
final ITriplePatternElement<CTT,NVT,AVT> subject,
final ITriplePatternElement<CTT,NVT,AVT> predicate,
final ITriplePatternElement<CTT,NVT,AVT> object )
{
super( fragmentURL, datasetURL, pageNumberWasRequested, pageNumber );
if ( subject == null )
throw new IllegalArgumentException();
if ( predicate == null )
throw new IllegalArgumentException();
if ( object == null )
throw new IllegalArgumentException();
this.subject = subject;
this.predicate = predicate;
this.object = object;
}
@Override
public ITriplePatternElement<CTT,NVT,AVT> getSubject() {
return subject;
}
@Override
public ITriplePatternElement<CTT,NVT,AVT> getPredicate() {
return predicate;
}
@Override
public ITriplePatternElement<CTT,NVT,AVT> getObject() {
return object;
}
@Override
public String toString()
{
return "TriplePatternFragmentRequest(" +
"class: " + getClass().getName() +
", subject: " + subject.toString() +
", predicate: " + predicate.toString() +
", object: " + object.toString() +
", fragmentURL: " + fragmentURL +
", isPageRequest: " + pageNumberWasRequested +
", pageNumber: " + pageNumber +
")";
}
}

View file

@ -0,0 +1,213 @@
package org.linkeddatafragments.servlet;
import com.google.gson.JsonObject;
import org.apache.jena.riot.Lang;
import org.linkeddatafragments.config.ConfigReader;
import org.linkeddatafragments.datasource.DataSourceFactory;
import org.linkeddatafragments.datasource.DataSourceTypesRegistry;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IDataSourceType;
import org.linkeddatafragments.datasource.index.IndexDataSource;
import org.linkeddatafragments.exceptions.DataSourceNotFoundException;
import org.linkeddatafragments.fragments.FragmentRequestParserBase;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.util.MIMEParse;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter;
import org.linkeddatafragments.views.LinkedDataFragmentWriterFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map.Entry;
/**
* Servlet that responds with a Linked Data Fragment.
*
* @author Ruben Verborgh
* @author Bart Hanssens
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class LinkedDataFragmentServlet extends HttpServlet {
private final static long serialVersionUID = 1L;
// Parameters
/**
*
*/
public final static String CFGFILE = "configFile";
private ConfigReader config;
private final HashMap<String, IDataSource> dataSources = new HashMap<>();
private final Collection<String> mimeTypes = new ArrayList<>();
private File getConfigFile(ServletConfig config) throws IOException {
String path = config.getServletContext().getRealPath("/");
if (path == null) {
// this can happen when running standalone
path = System.getProperty("user.dir");
}
File cfg = new File(path, "config-example.json");
if (config.getInitParameter(CFGFILE) != null) {
cfg = new File(config.getInitParameter(CFGFILE));
}
if (!cfg.exists()) {
throw new IOException("Configuration file " + cfg + " not found.");
}
if (!cfg.isFile()) {
throw new IOException("Configuration file " + cfg + " is not a file.");
}
return cfg;
}
/**
*
* @param servletConfig
* @throws ServletException
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
try {
// load the configuration
File configFile = getConfigFile(servletConfig);
config = new ConfigReader(new FileReader(configFile));
// register data source types
for ( Entry<String,IDataSourceType> typeEntry : config.getDataSourceTypes().entrySet() ) {
DataSourceTypesRegistry.register( typeEntry.getKey(),
typeEntry.getValue() );
}
// register data sources
for (Entry<String, JsonObject> dataSource : config.getDataSources().entrySet()) {
dataSources.put(dataSource.getKey(), DataSourceFactory.create(dataSource.getValue()));
}
// register content types
MIMEParse.register("text/html");
MIMEParse.register(Lang.TTL.getHeaderString());
MIMEParse.register(Lang.JSONLD.getHeaderString());
MIMEParse.register(Lang.NTRIPLES.getHeaderString());
MIMEParse.register(Lang.RDFXML.getHeaderString());
} catch (Exception e) {
throw new ServletException(e);
}
}
/**
*
*/
@Override
public void destroy()
{
for ( IDataSource dataSource : dataSources.values() ) {
try {
dataSource.close();
}
catch( Exception e ) {
// ignore
}
}
}
/**
* Get the datasource
*
* @param request
* @return
* @throws IOException
*/
private IDataSource getDataSource(HttpServletRequest request) throws DataSourceNotFoundException {
String contextPath = request.getContextPath();
String requestURI = request.getRequestURI();
String path = contextPath == null
? requestURI
: requestURI.substring(contextPath.length());
if (path.equals("/") || path.isEmpty()) {
final String baseURL = FragmentRequestParserBase.extractBaseURL(request, config);
return new IndexDataSource(baseURL, dataSources);
}
String dataSourceName = path.substring(1);
IDataSource dataSource = dataSources.get(dataSourceName);
if (dataSource == null) {
throw new DataSourceNotFoundException(dataSourceName);
}
return dataSource;
}
/**
*
* @param request
* @param response
* @throws ServletException
*/
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
ILinkedDataFragment fragment = null;
try {
// do conneg
String bestMatch = MIMEParse.bestMatch(request.getHeader("Accept"));
// set additional response headers
response.setHeader("Server", "Linked Data Fragments Server");
response.setContentType(bestMatch);
response.setCharacterEncoding("utf-8");
// create a writer depending on the best matching mimeType
ILinkedDataFragmentWriter writer = LinkedDataFragmentWriterFactory.create(config.getPrefixes(), dataSources, bestMatch);
try {
final IDataSource dataSource = getDataSource( request );
final ILinkedDataFragmentRequest ldfRequest =
dataSource.getRequestParser()
.parseIntoFragmentRequest( request, config );
fragment = dataSource.getRequestProcessor()
.createRequestedFragment( ldfRequest );
writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest);
} catch (DataSourceNotFoundException ex) {
try {
response.setStatus(404);
writer.writeNotFound(response.getOutputStream(), request);
} catch (Exception ex1) {
throw new ServletException(ex1);
}
} catch (Exception e) {
response.setStatus(500);
writer.writeError(response.getOutputStream(), e);
}
} catch (Exception e) {
throw new ServletException(e);
}
finally {
// close the fragment
if ( fragment != null ) {
try {
fragment.close();
}
catch ( Exception e ) {
// ignore
}
}
}
}
}

View file

@ -0,0 +1,139 @@
package org.linkeddatafragments.util;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.ResourceFactory;
/**
*
* @author mielvandersande
*/
@SuppressWarnings("javadoc")
/**
* All common URIs needed for parsing and serializations
*/
public class CommonResources {
/**
*
*/
public final static String RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
/**
*
*/
public final static Property RDF_TYPE = createProperty(RDF + "type");
/**
*
*/
public final static Property RDF_SUBJECT = createProperty(RDF + "subject");
/**
*
*/
public final static Property RDF_PREDICATE = createProperty(RDF + "predicate");
/**
*
*/
public final static Property RDF_OBJECT = createProperty(RDF + "object");
/**
*
*/
public final static String VOID = "http://rdfs.org/ns/void#";
/**
*
*/
public final static Property VOID_TRIPLES = createProperty(VOID + "triples");
/**
*
*/
public final static Property VOID_SUBSET = createProperty(VOID + "subset");
/**
*
*/
public final static Property VOID_DATASET = createProperty(VOID + "Dataset");
/**
*
*/
public final static String HYDRA = "http://www.w3.org/ns/hydra/core#";
/**
*
*/
public final static Property HYDRA_TOTALITEMS = createProperty(HYDRA + "totalItems");
/**
*
*/
public final static Property HYDRA_ITEMSPERPAGE = createProperty(HYDRA + "itemsPerPage");
/**
*
*/
public final static Property HYDRA_SEARCH = createProperty(HYDRA + "search");
/**
*
*/
public final static Property HYDRA_TEMPLATE = createProperty(HYDRA + "template");
/**
*
*/
public final static Property HYDRA_MAPPING = createProperty(HYDRA + "mapping");
/**
*
*/
public final static Property HYDRA_VARIABLE = createProperty(HYDRA + "variable");
/**
*
*/
public final static Property HYDRA_PROPERTY = createProperty(HYDRA + "property");
/**
*
*/
public final static Property HYDRA_COLLECTION = createProperty(HYDRA + "Collection");
/**
*
*/
public final static Property HYDRA_PAGEDCOLLECTION = createProperty(HYDRA + "PagedCollection");
/**
*
*/
public final static Property HYDRA_FIRSTPAGE = createProperty(HYDRA + "firstPage");
/**
*
*/
public final static Property HYDRA_LASTPAGE = createProperty(HYDRA + "lastPage");
/**
*
*/
public final static Property HYDRA_NEXTPAGE = createProperty(HYDRA + "nextPage");
/**
*
*/
public final static Property HYDRA_PREVIOUSPAGE = createProperty(HYDRA + "previousPage");
/**
*
*/
public final static Property INVALID_URI = createProperty("urn:invalid");
private static Property createProperty(String uri) {
return ResourceFactory.createProperty(uri);
}
}

View file

@ -0,0 +1,299 @@
package org.linkeddatafragments.util;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.linkeddatafragments.exceptions.NoRegisteredMimeTypesException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* MIME-Type Parser
*
* This class provides basic functions for handling mime-types. It can handle
* matching mime-types against a list of media-ranges. See section 14.1 of the
* HTTP specification [RFC 2616] for a complete explanation.
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
*
* A port to Java of Joe Gregorio's MIME-Type Parser:
*
* http://code.google.com/p/mimeparse/
*
* Ported by <a href="mailto:tzellman@gmail.com">Tom Zellman</a>.
* Extended by <a href="mailto:miel.vandersande@ugent.be">Miel Vander Sande</a>
*
*/
public final class MIMEParse
{
private final static List<String> mimeTypes = new ArrayList<>();
/**
* Register mimeType in collection
* @param mimeType
*/
public static void register(String mimeType) {
mimeTypes.add(mimeType);
}
/**
* Parse results container
*/
protected static class ParseResults
{
String type;
String subType;
// !a dictionary of all the parameters for the media range
Map<String, String> params;
@Override
public String toString()
{
StringBuffer s = new StringBuffer("('" + type + "', '" + subType
+ "', {");
for (String k : params.keySet())
s.append("'" + k + "':'" + params.get(k) + "',");
return s.append("})").toString();
}
}
/**
* Carves up a mime-type and returns a ParseResults object
*
* For example, the media range 'application/xhtml;q=0.5' would get parsed
* into:
*
* ('application', 'xhtml', {'q', '0.5'})
* @param mimeType
* @return
*/
protected static ParseResults parseMimeType(String mimeType)
{
String[] parts = StringUtils.split(mimeType, ";");
ParseResults results = new ParseResults();
results.params = new HashMap<String, String>();
for (int i = 1; i < parts.length; ++i)
{
String p = parts[i];
String[] subParts = StringUtils.split(p, '=');
if (subParts.length == 2)
results.params.put(subParts[0].trim(), subParts[1].trim());
}
String fullType = parts[0].trim();
// Java URLConnection class sends an Accept header that includes a
// single "*" - Turn it into a legal wildcard.
if (fullType.equals("*"))
fullType = "*/*";
String[] types = StringUtils.split(fullType, "/");
results.type = types[0].trim();
results.subType = types[1].trim();
return results;
}
/**
* Carves up a media range and returns a ParseResults.
*
* For example, the media range 'application/*;q=0.5' would get parsed into:
*
* ('application', '*', {'q', '0.5'})
*
* In addition this function also guarantees that there is a value for 'q'
* in the params dictionary, filling it in with a proper default if
* necessary.
*
* @param range
* @return
*/
protected static ParseResults parseMediaRange(String range)
{
ParseResults results = parseMimeType(range);
String q = results.params.get("q");
float f = NumberUtils.toFloat(q, 1);
if (StringUtils.isBlank(q) || f < 0 || f > 1)
results.params.put("q", "1");
return results;
}
/**
* Structure for holding a fitness/quality combo
*/
protected static class FitnessAndQuality implements
Comparable<FitnessAndQuality>
{
int fitness;
float quality;
String mimeType; // optionally used
/**
*
* @param fitness
* @param quality
*/
public FitnessAndQuality(int fitness, float quality)
{
this.fitness = fitness;
this.quality = quality;
}
public int compareTo(FitnessAndQuality o)
{
if (fitness == o.fitness)
{
if (quality == o.quality)
return 0;
else
return quality < o.quality ? -1 : 1;
}
else
return fitness < o.fitness ? -1 : 1;
}
}
/**
* Find the best match for a given mimeType against a list of media_ranges
* that have already been parsed by MimeParse.parseMediaRange(). Returns a
* tuple of the fitness value and the value of the 'q' quality parameter of
* the best match, or (-1, 0) if no match was found. Just as for
* quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
*
* @param mimeType
* @param parsedRanges
* @return
*/
protected static FitnessAndQuality fitnessAndQualityParsed(String mimeType,
Collection<ParseResults> parsedRanges)
{
int bestFitness = -1;
float bestFitQ = 0;
ParseResults target = parseMediaRange(mimeType);
for (ParseResults range : parsedRanges)
{
if ((target.type.equals(range.type) || range.type.equals("*") || target.type
.equals("*"))
&& (target.subType.equals(range.subType)
|| range.subType.equals("*") || target.subType
.equals("*")))
{
for (String k : target.params.keySet())
{
int paramMatches = 0;
if (!k.equals("q") && range.params.containsKey(k)
&& target.params.get(k).equals(range.params.get(k)))
{
paramMatches++;
}
int fitness = (range.type.equals(target.type)) ? 100 : 0;
fitness += (range.subType.equals(target.subType)) ? 10 : 0;
fitness += paramMatches;
if (fitness > bestFitness)
{
bestFitness = fitness;
bestFitQ = NumberUtils
.toFloat(range.params.get("q"), 0);
}
}
}
}
return new FitnessAndQuality(bestFitness, bestFitQ);
}
/**
* Find the best match for a given mime-type against a list of ranges that
* have already been parsed by parseMediaRange(). Returns the 'q' quality
* parameter of the best match, 0 if no match was found. This function
* bahaves the same as quality() except that 'parsed_ranges' must be a list
* of parsed media ranges.
*
* @param mimeType
* @param parsedRanges
* @return
*/
protected static float qualityParsed(String mimeType,
Collection<ParseResults> parsedRanges)
{
return fitnessAndQualityParsed(mimeType, parsedRanges).quality;
}
/**
* Returns the quality 'q' of a mime-type when compared against the
* mediaRanges in ranges. For example:
*
* @param mimeType
* @param ranges
* @return
*/
public static float quality(String mimeType, String ranges)
{
List<ParseResults> results = new LinkedList<ParseResults>();
for (String r : StringUtils.split(ranges, ','))
results.add(parseMediaRange(r));
return qualityParsed(mimeType, results);
}
/**
* Takes a list of supported mime-types and finds the best match for all the
* media-ranges listed in header. The value of header must be a string that
* conforms to the format of the HTTP Accept: header. The value of
* 'supported' is a list of mime-types.
*
* MimeParse.bestMatch(Arrays.asList(new String[]{"application/xbel+xml",
* "text/xml"}), "text/*;q=0.5,*; q=0.1") 'text/xml'
*
* @param supported
* @param header
* @return
* @throws org.linkeddatafragments.exceptions.NoRegisteredMimeTypesException
*/
public static String bestMatch(List<String> supported, String header) throws NoRegisteredMimeTypesException
{
if (supported.isEmpty())
throw new NoRegisteredMimeTypesException();
List<ParseResults> parseResults = new LinkedList<ParseResults>();
List<FitnessAndQuality> weightedMatches = new LinkedList<FitnessAndQuality>();
for (String r : StringUtils.split(header, ','))
parseResults.add(parseMediaRange(r));
for (String s : supported)
{
FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s,
parseResults);
fitnessAndQuality.mimeType = s;
weightedMatches.add(fitnessAndQuality);
}
Collections.sort(weightedMatches);
FitnessAndQuality lastOne = weightedMatches
.get(weightedMatches.size() - 1);
return NumberUtils.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType : supported.get(0);
}
/**
*
* @param header
* @return
* @throws NoRegisteredMimeTypesException
*/
public static String bestMatch(String header) throws NoRegisteredMimeTypesException
{
return bestMatch(mimeTypes, header);
}
// hidden
private MIMEParse()
{
}
}

View file

@ -0,0 +1,116 @@
package org.linkeddatafragments.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses strings (as obtained from HTTP request parameters) into RDF terms.
*
* @param <TermType> type for representing RDF terms
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
abstract public class RDFTermParser<TermType>
{
/**
*
*/
public static final Pattern STRINGPATTERN
= Pattern.compile("^\"(.*)\"(?:@(.*)|\\^\\^<?([^<>]*)>?)?$");
/**
*
* @param param
* @return
*/
public TermType parseIntoRDFNode( final String param )
{
if ( param == null || param.isEmpty() )
return handleUnparsableParameter( param );
// identify the kind of RDF term based on the first character
char firstChar = param.charAt(0);
switch ( firstChar )
{
// blank node
case '_':
return createBlankNode( param );
// angular brackets indicate a URI
case '<':
return createURI( param.substring(1, param.length()-1) );
// quotes indicate a string
case '"':
Matcher matcher = STRINGPATTERN.matcher( param );
if ( matcher.matches() ) {
String label = matcher.group(1);
String langTag = matcher.group(2);
String typeURI = matcher.group(3);
if ( langTag != null )
return createLanguageLiteral( label, langTag );
else if ( typeURI != null )
return createTypedLiteral( label, typeURI );
else
return createPlainLiteral( label );
}
else
return handleUnparsableParameter( param );
// assume it is a URI without angular brackets
default:
return createURI( param );
}
}
/**
*
* @param label
* @return
*/
abstract public TermType createBlankNode( final String label );
/**
*
* @param uri
* @return
*/
abstract public TermType createURI( final String uri );
/**
*
* @param label
* @param typeURI
* @return
*/
abstract public TermType createTypedLiteral( final String label,
final String typeURI );
/**
*
* @param label
* @param langTag
* @return
*/
abstract public TermType createLanguageLiteral( final String label,
final String langTag );
/**
*
* @param label
* @return
*/
abstract public TermType createPlainLiteral( final String label );
/**
*
* @param param
* @return
*/
abstract public TermType handleUnparsableParameter( final String param );
}

View file

@ -0,0 +1,80 @@
package org.linkeddatafragments.util;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.TriplePatternElementFactory;
/**
* Parses strings (as obtained from HTTP request parameters) into
* {@link ITriplePatternElement}s.
*
* @param <ConstantTermType> type for representing constants in triple patterns
* (i.e., URIs and literals)
* @param <NamedVarType> type for representing named variables in triple patterns
* @param <AnonVarType> type for representing anonymous variables in triple
* patterns (i.e., variables denoted by a blank node)
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
* @author Ruben Verborgh
*/
abstract public
class TriplePatternElementParser<ConstantTermType,NamedVarType,AnonVarType>
extends RDFTermParser<ConstantTermType>
{
/**
*
*/
public final TriplePatternElementFactory<ConstantTermType,NamedVarType,AnonVarType>
factory = new TriplePatternElementFactory<ConstantTermType,NamedVarType,AnonVarType>();
/**
*
* @param param
* @return
*/
public ITriplePatternElement<ConstantTermType,NamedVarType,AnonVarType>
parseIntoTriplePatternElement( final String param )
{
// nothing or empty indicates an unspecified variable
if ( param == null || param.isEmpty() )
return factory.createUnspecifiedVariable();
// identify the kind of RDF term based on the first character
char firstChar = param.charAt(0);
switch ( firstChar )
{
// specific variable that has a name
case '?':
{
final String varName = param.substring(1);
final NamedVarType var = createNamedVariable( varName );
return factory.createNamedVariable( var );
}
// specific variable that is denoted by a blank node
case '_':
{
final AnonVarType var = createAnonymousVariable( param );
return factory.createAnonymousVariable( var );
}
// assume it is an RDF term
default:
return factory.createConstantRDFTerm( parseIntoRDFNode(param) );
}
}
/**
*
* @param varName
* @return
*/
abstract public NamedVarType createNamedVariable( final String varName );
/**
*
* @param label
* @return
*/
abstract public AnonVarType createAnonymousVariable( final String label );
}

View file

@ -0,0 +1,128 @@
package org.linkeddatafragments.util;
import org.apache.jena.datatypes.RDFDatatype;
import org.apache.jena.datatypes.TypeMapper;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.ResourceFactory;
/**
* A {@link TriplePatternElementParser} for Jena-based backends.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class TriplePatternElementParserForJena
extends TriplePatternElementParser<RDFNode,String,String>
{
private static TriplePatternElementParserForJena instance = null;
/**
*
* @return
*/
public static TriplePatternElementParserForJena getInstance()
{
if ( instance == null ) {
instance = new TriplePatternElementParserForJena();
}
return instance;
}
/**
*
*/
protected TriplePatternElementParserForJena() {}
/**
*
* @param varName
* @return
*/
@Override
public String createNamedVariable( final String varName )
{
return varName;
}
/**
*
* @param label
* @return
*/
@Override
public String createAnonymousVariable( final String label )
{
return label;
}
/**
*
* @param label
* @return
*/
@Override
public RDFNode createBlankNode(final String label )
{
return ResourceFactory.createResource();
}
/**
*
* @param uri
* @return
*/
@Override
public RDFNode createURI(final String uri )
{
return ResourceFactory.createResource( uri );
}
/**
*
* @param label
* @param typeURI
* @return
*/
@Override
public RDFNode createTypedLiteral(final String label,
final String typeURI )
{
final RDFDatatype dt = TypeMapper.getInstance()
.getSafeTypeByName( typeURI );
return ResourceFactory.createTypedLiteral( label, dt );
}
/**
*
* @param label
* @param languageTag
* @return
*/
@Override
public RDFNode createLanguageLiteral(final String label,
final String languageTag )
{
return ResourceFactory.createLangLiteral( label, languageTag );
}
/**
*
* @param label
* @return
*/
@Override
public RDFNode createPlainLiteral(final String label )
{
return ResourceFactory.createPlainLiteral( label );
}
/**
*
* @param parameter
* @return
*/
@Override
public RDFNode handleUnparsableParameter(final String parameter )
{
return CommonResources.INVALID_URI;
}
}

View file

@ -0,0 +1,145 @@
package org.linkeddatafragments.views;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.index.IndexDataSource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//TODO: Refactor to a composable & flexible architecture using DataSource types, fragments types and request types
/**
* Serializes an {@link ILinkedDataFragment} to the HTML format
*
* @author Miel Vander Sande
*/
public class HtmlTriplePatternFragmentWriterImpl extends TriplePatternFragmentWriterBase implements ILinkedDataFragmentWriter {
private final Configuration cfg;
private final Template indexTemplate;
private final Template datasourceTemplate;
private final Template notfoundTemplate;
private final Template errorTemplate;
private final String HYDRA = "http://www.w3.org/ns/hydra/core#";
/**
*
* @param prefixes
* @param datasources
* @throws IOException
*/
public HtmlTriplePatternFragmentWriterImpl(Map<String, String> prefixes, HashMap<String, IDataSource> datasources) throws IOException {
super(prefixes, datasources);
cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setClassForTemplateLoading(getClass(), "/views");
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
indexTemplate = cfg.getTemplate("index.ftl.html");
datasourceTemplate = cfg.getTemplate("datasource.ftl.html");
notfoundTemplate = cfg.getTemplate("notfound.ftl.html");
errorTemplate = cfg.getTemplate("error.ftl.html");
}
/**
*
* @param outputStream
* @param datasource
* @param fragment
* @param tpfRequest
* @throws IOException
* @throws TemplateException
*/
@Override
public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ITriplePatternFragment fragment, ITriplePatternFragmentRequest tpfRequest) throws IOException, TemplateException{
Map data = new HashMap();
// base.ftl.html
data.put("assetsPath", "assets/");
data.put("header", datasource.getTitle());
data.put("date", new Date());
// fragment.ftl.html
data.put("datasourceUrl", tpfRequest.getDatasetURL());
data.put("datasource", datasource);
// Parse controls to template variables
StmtIterator controls = fragment.getControls();
while (controls.hasNext()) {
Statement control = controls.next();
String predicate = control.getPredicate().getURI();
RDFNode object = control.getObject();
if (!object.isAnon()) {
String value = object.isURIResource() ? object.asResource().getURI() : object.asLiteral().getLexicalForm();
data.put(predicate.replaceFirst(HYDRA, ""), value);
}
}
// Add metadata
data.put("totalEstimate", fragment.getTotalSize());
data.put("itemsPerPage", fragment.getMaxPageSize());
// Add triples and datasources
List<Statement> triples = fragment.getTriples().toList();
data.put("triples", triples);
data.put("datasources", getDatasources());
// Calculate start and end triple number
Long start = ((tpfRequest.getPageNumber() - 1) * fragment.getMaxPageSize()) + 1;
data.put("start", start);
data.put("end", start + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
// Compose query object
Map query = new HashMap();
query.put("subject", !tpfRequest.getSubject().isVariable() ? tpfRequest.getSubject().asConstantTerm() : "");
query.put("predicate", !tpfRequest.getPredicate().isVariable() ? tpfRequest.getPredicate().asConstantTerm() : "");
query.put("object", !tpfRequest.getObject().isVariable() ? tpfRequest.getObject().asConstantTerm() : "");
data.put("query", query);
// Get the template (uses cache internally)
Template temp = datasource instanceof IndexDataSource ? indexTemplate : datasourceTemplate;
// Merge data-model with template
temp.process(data, new OutputStreamWriter(outputStream));
}
@Override
public void writeNotFound(ServletOutputStream outputStream, HttpServletRequest request) throws Exception {
Map data = new HashMap();
data.put("assetsPath", "assets/");
data.put("datasources", getDatasources());
data.put("date", new Date());
data.put("url", request.getRequestURL().toString());
notfoundTemplate.process(data, new OutputStreamWriter(outputStream));
}
@Override
public void writeError(ServletOutputStream outputStream, Exception ex) throws Exception {
Map data = new HashMap();
data.put("assetsPath", "assets/");
data.put("date", new Date());
data.put("error", ex);
errorTemplate.process(data, new OutputStreamWriter(outputStream));
}
}

View file

@ -0,0 +1,44 @@
package org.linkeddatafragments.views;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
/**
* Represents a possible writer to serialize an {@link ILinkedDataFragment} object
*
* @author Miel Vander Sande
*/
public interface ILinkedDataFragmentWriter {
/**
* Writes a 404 Not Found error
*
* @param outputStream The response stream to write to
* @param request Request that is unable to answer
* @throws Exception Error that occurs while serializing
*/
public void writeNotFound(ServletOutputStream outputStream, HttpServletRequest request) throws Exception;
/**
* Writes a 5XX error
*
* @param outputStream The response stream to write to
* @param ex Exception that occurred
* @throws Exception Error that occurs while serializing
*/
public void writeError(ServletOutputStream outputStream, Exception ex) throws Exception;
/**
* Serializes and writes a {@link ILinkedDataFragment}
*
* @param outputStream The response stream to write to
* @param datasource
* @param fragment
* @param ldfRequest Parsed request for fragment
* @throws Exception Error that occurs while serializing
*/
public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ILinkedDataFragment fragment, ILinkedDataFragmentRequest ldfRequest) throws Exception;
}

View file

@ -0,0 +1,42 @@
package org.linkeddatafragments.views;
import org.linkeddatafragments.datasource.IDataSource;
import java.util.HashMap;
import java.util.Map;
/**
* Base class of any implementation of {@link ILinkedDataFragmentWriter}.
*
* @author Miel Vander Sande
*/
public abstract class LinkedDataFragmentWriterBase implements ILinkedDataFragmentWriter {
private final Map<String, String> prefixes;
private final HashMap<String, IDataSource> datasources;
/**
*
* @param prefixes
* @param datasources
*/
public LinkedDataFragmentWriterBase(Map<String, String> prefixes, HashMap<String, IDataSource> datasources) {
this.prefixes = prefixes;
this.datasources = datasources;
}
/**
*
* @return
*/
public Map<String, String> getPrefixes() {
return prefixes;
}
/**
*
* @return
*/
public HashMap<String, IDataSource> getDatasources() {
return datasources;
}
}

View file

@ -0,0 +1,35 @@
package org.linkeddatafragments.views;
import org.linkeddatafragments.datasource.IDataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* A factory for {@link ILinkedDataFragmentWriter}s.
*
* @author Miel Vander Sande
*/
public class LinkedDataFragmentWriterFactory {
private final static String HTML = "text/html";
/**
* Creates {@link ILinkedDataFragmentWriter} for a given mimeType
*
* @param prefixes Configured prefixes to be used in serialization
* @param datasources Configured datasources
* @param mimeType mimeType to create writer for
* @return created writer
* @throws IOException
*/
public static ILinkedDataFragmentWriter create(Map <String, String> prefixes, HashMap<String, IDataSource> datasources, String mimeType) throws IOException {
switch (mimeType) {
case HTML:
return new HtmlTriplePatternFragmentWriterImpl(prefixes, datasources);
default:
return new RdfWriterImpl(prefixes, datasources, mimeType);
}
}
}

View file

@ -0,0 +1,55 @@
package org.linkeddatafragments.views;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.riot.RDFLanguages;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Serializes an {@link ILinkedDataFragment} to an RDF format
*
* @author Miel Vander Sande
*/
public class RdfWriterImpl extends LinkedDataFragmentWriterBase implements ILinkedDataFragmentWriter {
private final Lang contentType;
public RdfWriterImpl(Map<String, String> prefixes, HashMap<String, IDataSource> datasources, String mimeType) {
super(prefixes, datasources);
this.contentType = RDFLanguages.contentTypeToLang(mimeType);
}
@Override
public void writeNotFound(ServletOutputStream outputStream, HttpServletRequest request) throws IOException {
outputStream.println(request.getRequestURL().toString() + " not found!");
outputStream.close();
}
@Override
public void writeError(ServletOutputStream outputStream, Exception ex) throws IOException {
outputStream.println(ex.getMessage());
outputStream.close();
}
@Override
public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ILinkedDataFragment fragment, ILinkedDataFragmentRequest ldfRequest) throws Exception {
final Model output = ModelFactory.createDefaultModel();
output.setNsPrefixes(getPrefixes());
output.add(fragment.getMetadata());
output.add(fragment.getTriples());
output.add(fragment.getControls());
RDFDataMgr.write(outputStream, output, contentType);
}
}

View file

@ -0,0 +1,46 @@
package org.linkeddatafragments.views;
import freemarker.template.TemplateException;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Base class of any implementation for ITriplePatternFragment.
*
* @author Miel Vander Sande
*/
public abstract class TriplePatternFragmentWriterBase extends LinkedDataFragmentWriterBase implements ILinkedDataFragmentWriter {
/**
*
* @param prefixes
* @param datasources
*/
public TriplePatternFragmentWriterBase(Map<String, String> prefixes, HashMap<String, IDataSource> datasources) {
super(prefixes, datasources);
}
@Override
public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ILinkedDataFragment fragment, ILinkedDataFragmentRequest ldfRequest) throws Exception {
writeFragment(outputStream, datasource, (ITriplePatternFragment) fragment, (ITriplePatternFragmentRequest) ldfRequest);
}
/**
*
* @param outputStream
* @param datasource
* @param fragment
* @param tpfRequest
* @throws IOException
* @throws TemplateException
*/
abstract public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ITriplePatternFragment fragment, ITriplePatternFragmentRequest tpfRequest) throws IOException, TemplateException;
}

View file

@ -0,0 +1,201 @@
package org.vivoweb.linkeddatafragments.datasource.rdfservice;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
import org.apache.jena.atlas.io.StringWriterI;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.QuerySolutionMap;
import org.apache.jena.query.ResultSet;
import org.apache.jena.query.Syntax;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.riot.out.NodeFormatter;
import org.apache.jena.riot.out.NodeFormatterTTL;
import org.apache.jena.tdb.TDBFactory;
import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import java.io.File;
public class RDFServiceBasedRequestProcessorForTPFs
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
{
private static RDFService rdfService;
public static void setRDFService(RDFService pRDFService) {
rdfService = pRDFService;
}
@Override
protected Worker getTPFSpecificWorker(
final ITriplePatternFragmentRequest<RDFNode,String,String> request )
throws IllegalArgumentException
{
return new Worker( request );
}
/**
*
*/
protected class Worker
extends AbstractRequestProcessorForTriplePatterns.Worker<RDFNode,String,String>
{
public Worker(
final ITriplePatternFragmentRequest<RDFNode,String,String> req )
{
super( req );
}
private void appendNode(StringBuilder builder, RDFNode node) {
if (node.isLiteral()) {
builder.append(literalToString(node.asLiteral()));
} else if (node.isURIResource()) {
builder.append('<' + node.asResource().getURI() + '>');
}
}
private String literalToString(Literal l) {
StringWriterI sw = new StringWriterI();
NodeFormatter fmt = new NodeFormatterTTL(null, null);
fmt.formatLiteral(sw, l.asNode());
return sw.toString();
}
@Override
protected ILinkedDataFragment createFragment(
final ITriplePatternElement<RDFNode,String,String> subject,
final ITriplePatternElement<RDFNode,String,String> predicate,
final ITriplePatternElement<RDFNode,String,String> object,
final long offset,
final long limit )
{
StringBuilder whereClause = new StringBuilder();
StringBuilder filter = new StringBuilder();
StringBuilder orderBy = new StringBuilder();
if ( ! subject.isVariable() ) {
appendNode(whereClause.append(' '), subject.asConstantTerm());
} else {
whereClause.append(" ?s");
if (filter.length() > 0) { filter.append(" && "); }
filter.append("!isBlank(?s)");
orderBy.append(" ?s");
}
if ( ! predicate.isVariable() ) {
appendNode(whereClause.append(' '), predicate.asConstantTerm());
} else {
whereClause.append(" ?p");
if (filter.length() > 0) { filter.append(" && "); }
filter.append("!isBlank(?p)");
orderBy.append(" ?p");
}
if ( ! object.isVariable() ) {
appendNode(whereClause.append(' '), object.asConstantTerm());
} else {
whereClause.append(" ?o");
if (filter.length() > 0) { filter.append(" && "); }
filter.append("!isBlank(?o)");
orderBy.append(" ?o");
}
StringBuilder constructQuery = new StringBuilder();
constructQuery.append("CONSTRUCT { ");
constructQuery.append(whereClause.toString());
constructQuery.append(" } WHERE { ");
constructQuery.append(whereClause.toString()).append(" . ");
if (filter.length() > 0) {
constructQuery.append(" FILTER(").append(filter.toString()).append(")");
}
constructQuery.append(" }");
if (orderBy.length() > 0) {
constructQuery.append(" ORDER BY").append(orderBy.toString());
}
if (limit > 0) {
constructQuery.append(" LIMIT ").append(limit);
}
if (offset > 0) {
constructQuery.append(" OFFSET ").append(offset);
}
Model triples = ModelFactory.createDefaultModel();
try {
rdfService.sparqlConstructQuery(constructQuery.toString(), triples);
} catch (RDFServiceException e) {
return createEmptyTriplePatternFragment();
}
if (triples.isEmpty()) {
return createEmptyTriplePatternFragment();
}
// Try to get an estimate
long size = triples.size();
long estimate = -1;
StringBuilder count = new StringBuilder();
count.append("SELECT (COUNT(*) AS ?count) WHERE { ");
count.append(whereClause.toString());
count.append(" . ");
if (filter.length() > 0) {
count.append(" FILTER(").append(filter.toString()).append(") ");
}
count.append(" }");
try {
CountConsumer countConsumer = new CountConsumer();
rdfService.sparqlSelectQuery(count.toString(), countConsumer);
estimate = countConsumer.estimate;
} catch (RDFServiceException e) {
return createEmptyTriplePatternFragment();
}
// No estimate or incorrect
if (estimate < offset + size) {
estimate = (size == limit) ? offset + size + 1 : offset + size;
}
// create the fragment
final boolean isLastPage = ( estimate < offset + limit );
return createTriplePatternFragment( triples, estimate, isLastPage );
}
} // end of class Worker
/**
* Constructor
*/
public RDFServiceBasedRequestProcessorForTPFs() {
}
class CountConsumer extends ResultSetConsumer {
public long estimate = -1;
@Override
protected void processQuerySolution(QuerySolution qs) {
if (estimate == -1) {
Literal literal = qs.getLiteral("count");
estimate = literal.getLong();
}
}
}
}

View file

@ -0,0 +1,46 @@
package org.vivoweb.linkeddatafragments.datasource.rdfservice;
import org.linkeddatafragments.datasource.DataSourceBase;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;
import java.io.File;
/**
* Experimental Jena TDB-backed data source of Basic Linked Data Fragments.
*
* @author <a href="mailto:bart.hanssens@fedict.be">Bart Hanssens</a>
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class RDFServiceDataSource extends DataSourceBase {
/**
* The request processor
*
*/
protected final RDFServiceBasedRequestProcessorForTPFs requestProcessor;
@Override
public IFragmentRequestParser getRequestParser()
{
return TPFRequestParserForJenaBackends.getInstance();
}
@Override
public IFragmentRequestProcessor getRequestProcessor()
{
return requestProcessor;
}
/**
* Constructor
*
* @param title
* @param description
*/
public RDFServiceDataSource(String title, String description) {
super(title, description);
requestProcessor = new RDFServiceBasedRequestProcessorForTPFs();
}
}

View file

@ -0,0 +1,31 @@
package org.vivoweb.linkeddatafragments.datasource.rdfservice;
import com.google.gson.JsonObject;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IDataSourceType;
import org.linkeddatafragments.exceptions.DataSourceCreationException;
import java.io.File;
/**
* The type of Triple Pattern Fragment data sources that are backed by
* a Jena TDB instance.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class RDFServiceDataSourceType implements IDataSourceType
{
@Override
public IDataSource createDataSource( final String title,
final String description,
final JsonObject settings )
throws DataSourceCreationException
{
try {
return new RDFServiceDataSource(title, description);
} catch (Exception ex) {
throw new DataSourceCreationException(ex);
}
}
}

View file

@ -0,0 +1,270 @@
package org.vivoweb.linkeddatafragments.servlet;
import com.google.gson.JsonObject;
import edu.cornell.mannlib.vitro.webapp.beans.Ontology;
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import org.apache.commons.io.IOUtils;
import org.apache.jena.riot.Lang;
import org.linkeddatafragments.config.ConfigReader;
import org.linkeddatafragments.datasource.DataSourceFactory;
import org.linkeddatafragments.datasource.DataSourceTypesRegistry;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IDataSourceType;
import org.linkeddatafragments.datasource.index.IndexDataSource;
import org.linkeddatafragments.exceptions.DataSourceNotFoundException;
import org.linkeddatafragments.fragments.FragmentRequestParserBase;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.util.MIMEParse;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter;
import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl;
import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceBasedRequestProcessorForTPFs;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSource;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSourceType;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
/**
* Servlet that responds with a Linked Data Fragment.
*/
public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
private final static long serialVersionUID = 1L;
private ConfigReader config;
private final HashMap<String, IDataSource> dataSources = new HashMap<>();
private final Collection<String> mimeTypes = new ArrayList<>();
private File getConfigFile(ServletConfig config) throws IOException {
String path = config.getServletContext().getRealPath("/");
if (path == null) {
// this can happen when running standalone
path = System.getProperty("user.dir");
}
File cfg = new File(path, "config-example.json");
if (!cfg.exists()) {
throw new IOException("Configuration file " + cfg + " not found.");
}
if (!cfg.isFile()) {
throw new IOException("Configuration file " + cfg + " is not a file.");
}
return cfg;
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
try {
ServletContext ctx = servletConfig.getServletContext();
RDFService rdfService = ModelAccess.on(ctx).getRDFService();
RDFServiceBasedRequestProcessorForTPFs.setRDFService(rdfService);
OntologyDao dao = ModelAccess.on(ctx).getWebappDaoFactory().getOntologyDao();
// load the configuration
config = new ConfigReader(new StringReader(getConfigJson(dao)));
// register data source types
for ( Entry<String,IDataSourceType> typeEntry : config.getDataSourceTypes().entrySet() ) {
if (!DataSourceTypesRegistry.isRegistered(typeEntry.getKey())) {
DataSourceTypesRegistry.register( typeEntry.getKey(),
typeEntry.getValue() );
}
}
// register data sources
for (Entry<String, JsonObject> dataSource : config.getDataSources().entrySet()) {
dataSources.put(dataSource.getKey(), DataSourceFactory.create(dataSource.getValue()));
}
// register content types
MIMEParse.register("text/html");
MIMEParse.register(Lang.TTL.getHeaderString());
MIMEParse.register(Lang.JSONLD.getHeaderString());
MIMEParse.register(Lang.NTRIPLES.getHeaderString());
MIMEParse.register(Lang.RDFXML.getHeaderString());
HtmlTriplePatternFragmentWriterImpl.setContextPath(servletConfig.getServletContext().getContextPath());
} catch (Exception e) {
throw new ServletException(e);
}
}
@Override
public void destroy()
{
for ( IDataSource dataSource : dataSources.values() ) {
try {
dataSource.close();
}
catch( Exception e ) {
// ignore
}
}
}
private IDataSource getDataSource(HttpServletRequest request) throws DataSourceNotFoundException {
String contextPath = request.getContextPath();
String requestURI = request.getRequestURI();
String path = contextPath == null
? requestURI
: requestURI.substring(contextPath.length());
if (path.startsWith("/tpf")) {
path = path.substring(4);
}
if (path.equals("/") || path.isEmpty()) {
final String baseURL = FragmentRequestParserBase.extractBaseURL(request, config);
return new IndexDataSource(baseURL, dataSources);
}
String dataSourceName = path.substring(1);
IDataSource dataSource = dataSources.get(dataSourceName);
if (dataSource == null) {
throw new DataSourceNotFoundException(dataSourceName);
}
return dataSource;
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
int fileNamePos = request.getRequestURI().toLowerCase().lastIndexOf("/tpf/assets/");
if (fileNamePos > 0) {
try {
String fileName = request.getRequestURI().substring(fileNamePos + 12);
InputStream in = VitroLinkedDataFragmentServlet.class.getResourceAsStream(fileName);
if (in != null) {
IOUtils.copy(in, response.getOutputStream());
}
return;
} catch (IOException ioe) {
}
}
ILinkedDataFragment fragment = null;
try {
// do conneg
String bestMatch = MIMEParse.bestMatch(request.getHeader("Accept"));
// set additional response headers
response.setHeader("Server", "Linked Data Fragments Server");
response.setContentType(bestMatch);
response.setCharacterEncoding("utf-8");
// create a writer depending on the best matching mimeType
ILinkedDataFragmentWriter writer = LinkedDataFragmentWriterFactory.create(config.getPrefixes(), dataSources, bestMatch);
try {
final IDataSource dataSource = getDataSource( request );
final ILinkedDataFragmentRequest ldfRequest =
dataSource.getRequestParser()
.parseIntoFragmentRequest( request, config );
fragment = dataSource.getRequestProcessor()
.createRequestedFragment( ldfRequest );
response.setHeader("Access-Control-Allow-Origin", "*");
writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest);
} catch (DataSourceNotFoundException ex) {
try {
response.setStatus(404);
writer.writeNotFound(response.getOutputStream(), request);
} catch (Exception ex1) {
throw new ServletException(ex1);
}
} catch (Exception e) {
response.setStatus(500);
writer.writeError(response.getOutputStream(), e);
}
} catch (Exception e) {
throw new ServletException(e);
}
finally {
// close the fragment
if ( fragment != null ) {
try {
fragment.close();
}
catch ( Exception e ) {
// ignore
}
}
}
}
private String getConfigJson(OntologyDao dao) {
StringBuilder configJson = new StringBuilder();
configJson.append("{\n");
configJson.append(" \"title\": \"Linked Data Fragments server\",\n");
configJson.append("\n");
configJson.append(" \"datasourcetypes\": {\n");
configJson.append(" \"RDFServiceDatasource\": \"" + RDFServiceDataSourceType.class.getCanonicalName() + "\"\n");
configJson.append(" },\n");
configJson.append("\n");
configJson.append(" \"datasources\": {\n");
configJson.append(" \"core\": {\n");
configJson.append(" \"title\": \"core\",\n");
configJson.append(" \"type\": \"RDFServiceDatasource\",\n");
configJson.append(" \"description\": \"All data\"\n");
configJson.append(" }\n");
configJson.append(" },\n");
configJson.append("\n");
configJson.append(" \"prefixes\": {\n");
configJson.append(" \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n");
configJson.append(" \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n");
configJson.append(" \"hydra\": \"http://www.w3.org/ns/hydra/core#\",\n");
configJson.append(" \"void\": \"http://rdfs.org/ns/void#\"");
List<Ontology> onts = dao.getAllOntologies();
if (onts != null) {
for (Ontology ont : onts) {
switch (ont.getPrefix()) {
case "rdf":
case "rdfs":
case "hydra":
case "void":
break;
default:
configJson.append(",\n");
configJson.append(" \"");
configJson.append(ont.getPrefix());
configJson.append("\": \"");
configJson.append(ont.getURI());
configJson.append("\"");
break;
}
}
}
configJson.append(" }\n");
configJson.append("}\n");
return configJson.toString();
}
}

View file

@ -0,0 +1,221 @@
package org.vivoweb.linkeddatafragments.views;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.apache.jena.atlas.io.StringWriterI;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.rdf.model.impl.LiteralImpl;
import org.apache.jena.riot.out.NodeFormatter;
import org.apache.jena.riot.out.NodeFormatterTTL;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.index.IndexDataSource;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter;
import org.linkeddatafragments.views.TriplePatternFragmentWriterBase;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//TODO: Refactor to a composable & flexible architecture using DataSource types, fragments types and request types
/**
* Serializes an {@link ILinkedDataFragment} to the HTML format
*
* @author Miel Vander Sande
*/
public class HtmlTriplePatternFragmentWriterImpl extends TriplePatternFragmentWriterBase implements ILinkedDataFragmentWriter {
private final Configuration cfg;
private final Template indexTemplate;
private final Template datasourceTemplate;
private final Template notfoundTemplate;
private final Template errorTemplate;
private final String HYDRA = "http://www.w3.org/ns/hydra/core#";
private static String contextPath;
public static void setContextPath(String path) {
contextPath = path;
if (!contextPath.endsWith("/")) {
contextPath += "/";
}
}
/**
*
* @param prefixes
* @param datasources
* @throws IOException
*/
public HtmlTriplePatternFragmentWriterImpl(Map<String, String> prefixes, HashMap<String, IDataSource> datasources) throws IOException {
super(prefixes, datasources);
cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setClassForTemplateLoading(getClass(), "/tpf");
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
indexTemplate = cfg.getTemplate("index.ftl.html");
datasourceTemplate = cfg.getTemplate("datasource.ftl.html");
notfoundTemplate = cfg.getTemplate("notfound.ftl.html");
errorTemplate = cfg.getTemplate("error.ftl.html");
}
/**
*
* @param outputStream
* @param datasource
* @param fragment
* @param tpfRequest
* @throws IOException
* @throws TemplateException
*/
@Override
public void writeFragment(ServletOutputStream outputStream, IDataSource datasource, ITriplePatternFragment fragment, ITriplePatternFragmentRequest tpfRequest) throws IOException, TemplateException{
Map data = new HashMap();
// base.ftl.html
data.put("homePath", (contextPath != null ? contextPath : "") + "tpf");
data.put("assetsPath", (contextPath != null ? contextPath : "") + "tpf/assets/");
data.put("header", datasource.getTitle());
data.put("date", new Date());
// fragment.ftl.html
data.put("datasourceUrl", tpfRequest.getDatasetURL());
data.put("datasource", datasource);
// Parse controls to template variables
StmtIterator controls = fragment.getControls();
while (controls.hasNext()) {
Statement control = controls.next();
String predicate = control.getPredicate().getURI();
RDFNode object = control.getObject();
if (!object.isAnon()) {
String value = object.isURIResource() ? object.asResource().getURI() : object.asLiteral().getLexicalForm();
data.put(predicate.replaceFirst(HYDRA, ""), value);
}
}
// Add metadata
data.put("totalEstimate", fragment.getTotalSize());
data.put("itemsPerPage", fragment.getMaxPageSize());
// Add triples and datasources
List<Statement> triples = fragment.getTriples().toList();
data.put("triples", triples);
data.put("datasources", getDatasources());
// Calculate start and end triple number
Long start = ((tpfRequest.getPageNumber() - 1) * fragment.getMaxPageSize()) + 1;
data.put("start", start);
data.put("end", start + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
// Compose query object
Map query = new HashMap();
query.put("subject", !tpfRequest.getSubject().isVariable() ? handleCT(tpfRequest.getSubject().asConstantTerm()) : "");
query.put("predicate", !tpfRequest.getPredicate().isVariable() ? handleCT(tpfRequest.getPredicate().asConstantTerm()) : "");
query.put("object", !tpfRequest.getObject().isVariable() ? handleCT(tpfRequest.getObject().asConstantTerm()) : "");
query.put("pattern", makeQueryPattern(tpfRequest));
data.put("query", query);
// Get the template (uses cache internally)
Template temp = datasource instanceof IndexDataSource ? indexTemplate : datasourceTemplate;
// Merge data-model with template
temp.process(data, new OutputStreamWriter(outputStream));
}
private String makeQueryPattern(ITriplePatternFragmentRequest tpfRequest) {
StringBuilder pattern = new StringBuilder();
ITriplePatternElement<RDFNode,String,String> subject = tpfRequest.getSubject();
ITriplePatternElement<RDFNode,String,String> predicate = tpfRequest.getPredicate();
ITriplePatternElement<RDFNode,String,String> object = tpfRequest.getObject();
pattern.append("{");
if ( ! subject.isVariable() ) {
appendNode(pattern.append(' '), subject.asConstantTerm());
} else {
pattern.append(" ?s");
}
if ( ! predicate.isVariable() ) {
appendNode(pattern.append(' '), predicate.asConstantTerm());
} else {
pattern.append(" ?p");
}
if ( ! object.isVariable() ) {
appendNode(pattern.append(' '), object.asConstantTerm());
} else {
pattern.append(" ?o");
}
pattern.append(" }");
return pattern.toString();
}
private void appendNode(StringBuilder builder, RDFNode node) {
if (node.isLiteral()) {
builder.append(literalToString(node.asLiteral()));
} else if (node.isURIResource()) {
builder.append('<' + node.asResource().getURI() + '>');
}
}
private String literalToString(Literal l) {
StringWriterI sw = new StringWriterI();
NodeFormatter fmt = new NodeFormatterTTL(null, null);
fmt.formatLiteral(sw, l.asNode());
return sw.toString();
}
private Object handleCT(Object obj) {
if (obj instanceof LiteralImpl) {
return ((LiteralImpl)obj).asNode().toString();
}
return obj;
}
@Override
public void writeNotFound(ServletOutputStream outputStream, HttpServletRequest request) throws Exception {
Map data = new HashMap();
data.put("homePath", (contextPath != null ? contextPath : "") + "tpf");
data.put("assetsPath", (contextPath != null ? contextPath : "") + "tpf/assets/");
data.put("datasources", getDatasources());
data.put("date", new Date());
data.put("url", request.getRequestURL().toString());
notfoundTemplate.process(data, new OutputStreamWriter(outputStream));
}
@Override
public void writeError(ServletOutputStream outputStream, Exception ex) throws Exception {
Map data = new HashMap();
data.put("homePath", (contextPath != null ? contextPath : "") + "tpf");
data.put("assetsPath", (contextPath != null ? contextPath : "") + "tpf/assets/");
data.put("date", new Date());
data.put("error", ex);
errorTemplate.process(data, new OutputStreamWriter(outputStream));
}
}

View file

@ -0,0 +1,36 @@
package org.vivoweb.linkeddatafragments.views;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter;
import org.linkeddatafragments.views.RdfWriterImpl;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* A factory for {@link ILinkedDataFragmentWriter}s.
*
* @author Miel Vander Sande
*/
public class LinkedDataFragmentWriterFactory {
private final static String HTML = "text/html";
/**
* Creates {@link ILinkedDataFragmentWriter} for a given mimeType
*
* @param prefixes Configured prefixes to be used in serialization
* @param datasources Configured datasources
* @param mimeType mimeType to create writer for
* @return created writer
*/
public static ILinkedDataFragmentWriter create(Map <String, String> prefixes, HashMap<String, IDataSource> datasources, String mimeType) throws IOException {
switch (mimeType) {
case HTML:
return new HtmlTriplePatternFragmentWriterImpl(prefixes, datasources);
default:
return new RdfWriterImpl(prefixes, datasources, mimeType);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="400px" height="220px" viewBox="0 0 400 220" enable-background="new 0 0 400 220" xml:space="preserve">
<g>
<path fill="#1D1D1B" d="M188.8,29.3v99.7h65.4v22.9h-89.9V29.3H188.8z"/>
<path fill="#1D1D1B" d="M266,151.8V29.2h47.2c38.1,0,57.2,20.5,57.2,61.3c0,40.8-19.1,61.3-57.2,61.3H266z M313.2,127.4
c21.7-0.1,32.6-12.3,32.7-36.8c-0.1-24.5-10.9-36.8-32.7-36.8h-22.7v73.6H313.2z"/>
</g>
<g>
<path fill="#1D1D1B" d="M29.2,170.4h4.3v20.5h10v3.6H29.2V170.4z"/>
<path fill="#1D1D1B" d="M45.7,170.8c0-1.4,1.1-2.4,2.6-2.4c1.5,0,2.6,1,2.6,2.4c0,1.4-1.1,2.4-2.6,2.4
C46.8,173.2,45.7,172.2,45.7,170.8z M46.2,176.4h4.2v18.1h-4.2V176.4z"/>
<path fill="#1D1D1B" d="M54.8,176.4h3.5l0.3,2.4h0.1c1.6-1.6,3.5-2.9,5.9-2.9c3.9,0,5.6,2.6,5.6,7.2v11.3H66v-10.8
c0-3-0.8-4.1-2.8-4.1c-1.6,0-2.6,0.8-4.1,2.2v12.6h-4.2V176.4z"/>
<path fill="#1D1D1B" d="M74.3,168.5h4.2v16.2h0.1l6.7-8.3h4.6l-6.2,7.4l6.8,10.7h-4.6l-4.6-7.8l-2.8,3.2v4.6h-4.2V168.5z"/>
<path fill="#1D1D1B" d="M98.7,175.9c4.9,0,7.5,3.5,7.5,8.6c0,0.8-0.1,1.5-0.2,2H94.6c0.4,3.3,2.4,5.1,5.3,5.1
c1.5,0,2.9-0.5,4.2-1.3l1.4,2.7c-1.7,1.1-3.9,2-6.2,2c-5,0-8.9-3.5-8.9-9.5C90.4,179.5,94.5,175.9,98.7,175.9z M102.5,183.8
c0-2.9-1.2-4.6-3.7-4.6c-2.1,0-4,1.6-4.3,4.6H102.5z"/>
<path fill="#1D1D1B" d="M115.8,175.9c2.1,0,3.3,0.8,4.7,2l-0.1-2.9v-6.6h4.2v26h-3.5l-0.3-2h-0.1c-1.3,1.3-3.2,2.4-5.1,2.4
c-4.5,0-7.4-3.5-7.4-9.5C108.1,179.5,111.8,175.9,115.8,175.9z M116.6,191.4c1.4,0,2.6-0.6,3.8-2v-8.4c-1.3-1.1-2.5-1.5-3.7-1.5
c-2.3,0-4.2,2.2-4.2,6C112.5,189.3,113.9,191.4,116.6,191.4z"/>
<path fill="#1D1D1B" d="M135.7,170.4h6.4c7.4,0,11.7,4,11.7,11.9c0,8-4.4,12.1-11.5,12.1h-6.6V170.4z M141.8,191
c4.9,0,7.7-2.8,7.7-8.7c0-5.9-2.8-8.5-7.7-8.5H140V191H141.8z"/>
<path fill="#1D1D1B" d="M167.1,182.9c0-1.9-0.8-3.5-3.3-3.5c-1.8,0-3.5,0.8-5.1,1.8l-1.5-2.8c2-1.3,4.5-2.4,7.4-2.4
c4.5,0,6.7,2.8,6.7,7.8v10.7h-3.5l-0.3-2h-0.1c-1.6,1.4-3.5,2.4-5.6,2.4c-3.2,0-5.4-2.1-5.4-5.3
C156.5,185.7,159.7,183.7,167.1,182.9z M163.2,191.6c1.5,0,2.6-0.7,3.9-2v-4c-4.9,0.6-6.5,1.9-6.5,3.7
C160.6,190.9,161.6,191.6,163.2,191.6z"/>
<path fill="#1D1D1B" d="M176,179.7h-2.6v-3.2l2.8-0.2l0.5-4.9h3.5v4.9h4.6v3.3h-4.6v8.6c0,2.1,0.8,3.2,2.5,3.2
c0.6,0,1.4-0.2,1.9-0.4l0.7,3.1c-1,0.3-2.2,0.7-3.7,0.7c-4.2,0-5.7-2.6-5.7-6.6V179.7z"/>
<path fill="#1D1D1B" d="M196.9,182.9c0-1.9-0.8-3.5-3.3-3.5c-1.8,0-3.5,0.8-5.1,1.8l-1.5-2.8c2-1.3,4.5-2.4,7.4-2.4
c4.5,0,6.7,2.8,6.7,7.8v10.7h-3.5l-0.3-2h-0.1c-1.6,1.4-3.5,2.4-5.6,2.4c-3.2,0-5.4-2.1-5.4-5.3
C186.3,185.7,189.5,183.7,196.9,182.9z M193,191.6c1.5,0,2.6-0.7,3.9-2v-4c-4.9,0.6-6.5,1.9-6.5,3.7
C190.4,190.9,191.5,191.6,193,191.6z"/>
<path fill="#1D1D1B" d="M212.1,170.4h14.5v3.6h-10.3v6.8h8.7v3.6h-8.7v10h-4.3V170.4z"/>
<path fill="#1D1D1B" d="M228.6,176.4h3.5l0.3,3.2h0.1c1.3-2.4,3.2-3.6,5.1-3.6c0.9,0,1.5,0.1,2.1,0.4l-0.7,3.7
c-0.6-0.2-1.1-0.3-1.9-0.3c-1.4,0-3.2,1-4.3,3.7v11h-4.2V176.4z"/>
<path fill="#1D1D1B" d="M250.1,182.9c0-1.9-0.8-3.5-3.3-3.5c-1.8,0-3.5,0.8-5.1,1.8l-1.5-2.8c2-1.3,4.5-2.4,7.4-2.4
c4.5,0,6.7,2.8,6.7,7.8v10.7h-3.5l-0.3-2h-0.1c-1.6,1.4-3.5,2.4-5.6,2.4c-3.2,0-5.4-2.1-5.4-5.3
C239.5,185.7,242.7,183.7,250.1,182.9z M246.1,191.6c1.5,0,2.6-0.7,3.9-2v-4c-4.9,0.6-6.5,1.9-6.5,3.7
C243.6,190.9,244.6,191.6,246.1,191.6z"/>
<path fill="#1D1D1B" d="M259.8,193.9v-0.1c-0.9-0.6-1.6-1.5-1.6-3c0-1.4,1-2.6,2-3.3v-0.1c-1.2-0.9-2.4-2.7-2.4-4.8
c0-4.2,3.3-6.5,7.1-6.5c1,0,1.9,0.2,2.6,0.4h6.5v3.1h-3.3c0.6,0.7,1,1.8,1,3.1c0,4-3,6.1-6.8,6.1c-0.8,0-1.7-0.1-2.5-0.5
c-0.6,0.5-0.9,0.9-0.9,1.7c0,1,0.7,1.7,2.9,1.7h3.2c4.3,0,6.6,1.3,6.6,4.5c0,3.6-3.8,6.4-9.6,6.4c-4.3,0-7.5-1.5-7.5-4.7
C257.2,196.2,258.1,194.9,259.8,193.9z M265.4,199.7c2.9,0,4.9-1.4,4.9-2.9c0-1.4-1.1-1.8-3.2-1.8h-2.5c-1,0-1.8-0.1-2.5-0.3
c-1,0.7-1.4,1.5-1.4,2.4C260.7,198.7,262.5,199.7,265.4,199.7z M268,182.4c0-2.2-1.4-3.6-3.1-3.6s-3.2,1.3-3.2,3.6
c0,2.3,1.4,3.6,3.2,3.6C266.6,186.1,268,184.7,268,182.4z"/>
<path fill="#1D1D1B" d="M276.5,176.4h3.5l0.3,2.5h0.1c1.5-1.6,3.3-2.9,5.5-2.9c2.6,0,4.2,1.2,5,3.2c1.7-1.8,3.5-3.2,5.8-3.2
c3.8,0,5.6,2.6,5.6,7.2v11.3h-4.3v-10.8c0-3-0.9-4.1-2.8-4.1c-1.1,0-2.4,0.7-3.8,2.2v12.6h-4.2v-10.8c0-3-0.9-4.1-2.8-4.1
c-1.1,0-2.4,0.7-3.8,2.2v12.6h-4.2V176.4z"/>
<path fill="#1D1D1B" d="M313.6,175.9c4.9,0,7.5,3.5,7.5,8.6c0,0.8-0.1,1.5-0.2,2h-11.5c0.4,3.3,2.4,5.1,5.3,5.1
c1.5,0,2.9-0.5,4.2-1.3l1.4,2.7c-1.7,1.1-3.9,2-6.2,2c-5,0-8.9-3.5-8.9-9.5C305.3,179.5,309.3,175.9,313.6,175.9z M317.4,183.8
c0-2.9-1.2-4.6-3.7-4.6c-2.1,0-4,1.6-4.3,4.6H317.4z"/>
<path fill="#1D1D1B" d="M324,176.4h3.5l0.3,2.4h0.1c1.6-1.6,3.5-2.9,5.9-2.9c3.9,0,5.6,2.6,5.6,7.2v11.3h-4.2v-10.8
c0-3-0.8-4.1-2.8-4.1c-1.6,0-2.6,0.8-4.1,2.2v12.6H324V176.4z"/>
<path fill="#1D1D1B" d="M344.2,179.7h-2.6v-3.2l2.8-0.2l0.5-4.9h3.5v4.9h4.6v3.3h-4.6v8.6c0,2.1,0.8,3.2,2.5,3.2
c0.6,0,1.4-0.2,1.9-0.4l0.7,3.1c-1,0.3-2.2,0.7-3.7,0.7c-4.2,0-5.7-2.6-5.7-6.6V179.7z"/>
<path fill="#1D1D1B" d="M356,189.7c1.6,1.3,3.2,2,5,2c2,0,2.9-0.9,2.9-2.2c0-1.5-2-2.2-3.9-2.9c-2.4-0.9-5.1-2.2-5.1-5.3
c0-3.2,2.5-5.4,6.6-5.4c2.5,0,4.5,1,6,2.2l-2,2.6c-1.3-0.9-2.5-1.5-4-1.5c-1.8,0-2.6,0.8-2.6,2c0,1.4,1.8,2,3.8,2.7
c2.5,0.9,5.2,2.1,5.2,5.5c0,3.1-2.5,5.6-7,5.6c-2.5,0-5.1-1.1-6.8-2.5L356,189.7z"/>
</g>
<g>
<path fill="#BE1622" d="M46.9,122.8H30.3l2.3-18.7H51l6.2-28.5H39.5l2.2-18h19.4l6.1-28.1h23.3l-6.1,28.1h24.5l6-28.1H138
l-6.1,28.1h18.2l-2.2,18H128l-6.2,28.5h18.6l-2.3,18.7h-20.4l-6.3,29.1H88.8l6.2-29.1H70l-6.3,29.1h-23L46.9,122.8z M98.9,104.1
l6.1-28.5H80.4l-6.3,28.5H98.9z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,247 @@
/*! @license ©2013 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
html, input, th, td {
font-family: "Open Sans", Verdana, Arial, sans-serif;
font-size: 11pt;
}
html {
background: #f6f6f6;
}
body {
max-width: 800px;
margin: 0 auto;
line-height: 1.3;
color: #333333;
background-color: white;
padding: 10px 40px;
box-shadow: 2px 2px 15px 0px rgba(50, 50, 50, 0.75);
}
h1, h2, h3, legend {
margin: .4em 0 .2em;
overflow: hidden;
}
h1 {
margin-right: 180px;
}
h1 a {
color: black;
}
h2 {
color: #be1622;
}
h3 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
p {
margin: 0;
}
a {
color: #be1622;
text-decoration: none;
border-bottom: none 1px;
}
a:hover {
color: #be1622 !important;
border-bottom-style: solid;
}
ul {
padding: 0;
margin: 0 0 .5em 1.5em;
list-style: none;
}
pre {
margin: 0;
}
form {
margin: 0 0 1.5em;
}
fieldset {
border: none;
padding: .5em 0 0 20px;
}
fieldset ul {
margin-left: 0;
}
fieldset li {
line-height: 2em;
}
legend {
font-size: 1.17em;
font-weight: bold;
padding: 0;
margin-left: -20px;
}
label {
width: 100px;
display: block;
float: left;
clear: both;
font-weight: bold;
}
label:after {
content: ":";
}
input {
outline: none;
font-size: .95em;
}
fieldset input {
width: 500px;
color: #be1622;
background-color: transparent;
border: none;
border-bottom: 1px solid #bbbbbb;
cursor: pointer;
}
input[type=submit] {
font-weight: bold;
color: #be1622;
background-color: #f6f6f6;
border-radius: 3px;
padding: 5px 8px;
border: 1px solid #999999;
cursor: pointer;
}
input[type=submit]:hover {
border-color: #666666;
}
input[type=submit]:active {
padding: 6px 7px 4px 9px;
}
.uri {
font-family: "Droid Sans Mono", monospace;
}
header .logo {
text-align: right;
}
header .logo a {
position: absolute;
top: 20px;
margin-left: -100px;
border-bottom-width: 0px;
}
header .logo img {
width: 160px;
}
footer {
clear: both;
margin: 1.5em 0 .5em;
font-size: small;
}
footer * {
color: gray;
margin-right: 5px;
}
.counts {
color: gray;
}
ul.links {
margin: 0;
padding: 0;
display: inline;
}
ul.links li {
display: inline;
padding-left: 20px;
font-weight: bold;
}
ul.triples {
margin: .3em 0 1em 20px;
font-size: .95em;
line-height: 1.5;
font-family: "Droid Sans Mono", monospace;
overflow-x: hidden;
}
ul.triples li {
text-indent: -20px;
padding-left: 20px;
max-width: 100%;
max-height: 1.5em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
ul.triples li:hover {
max-height: 100em;
white-space: normal;
transition: max-height .5s ease-in;
transition-delay: .5s;
}
ul.triples li:not(:hover) a {
color: inherit;
}
ul.triples a:nth-child(2) {
margin: 0 1em;
}
abbr {
border: none;
}
.index {
margin-bottom: 2em;
}
.datasets {
margin: .5em 20px;
}
dt {
font-weight: bold;
display: block;
float: left;
clear: left;
}
dd {
color: gray;
margin: .1em 0 0 12em;
font-size: .95em;
}
#about {
margin-top: 1.5em;
font-size: .9em;
}
@media screen and (max-width: 700px) {
html, input, th, td {
font-size: 10pt;
}
body {
padding: 15px;
}
header figure {
display: none;
}
h1, legend {
margin: 0;
}
fieldset, ul.triples {
padding: .5em 0;
margin: 0;
}
fieldset input {
width: 70%;
}
label {
width: 80px;
}
ul.triples li {
margin: 1em 0;
}
}

View file

@ -0,0 +1,30 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#macro display_page>
<!DOCTYPE html>
<html lang="en" prefix="hydra: http://www.w3.org/ns/hydra/core# void: http://rdfs.org/ns/void#">
<head>
<meta charset="utf-8">
<title>Linked Data Fragments Server ${ (title!header)?ensure_starts_with("(")?ensure_ends_with(")") }</title>
<link rel="stylesheet" href="${ assetsPath }style.css" />
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:700italic,400,700|Droid+Sans+Mono" type="text/css" />
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1">
</head>
<body>
<header>
<h1><a href="${homePath}">Linked Data Fragments Server</a></h1>
<figure class="logo">
<a href="http://linkeddatafragments.org/"><img src="${ assetsPath }logo.svg" alt="Linked Data Fragments" /></a>
</figure>
</header>
<main>
<@contents/>
</main>
<footer>
<p>
Powered by a <a href="https://github.com/LinkedDataFragments/Server.java" target="_blank">Linked Data Fragments Server</a>
©2013${date?string.yyyy} Multimedia Lab iMinds Ghent University
</p>
</footer>
</body>
</html>
</#macro>

View file

@ -0,0 +1,7 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#assign title = datasource.getTitle() + ' | ' + title!"">
<#include "base.ftl.html">
<#macro contents>
<#include "fragment.ftl.html">
</#macro>
<@display_page/>

View file

@ -0,0 +1,11 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#include "base.ftl.html">
<#macro contents>
<h2>Error executing your request</h2>
<p>Your request could not be executed due to an internal server error.</p>
<p>Please try reloading the page or return to the <a href="/">index page</a>.</p>
<h3>Error details</h3>
<p><#if error??>${(error.getMessage())!error!""}</#if><p>
</#macro>
<@display_page/>

View file

@ -0,0 +1,83 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#setting url_escaping_charset='UTF-8'>
<div resource="${datasourceUrl}" typeof="void:datasource hydra:Collection">
<h2><a href="${datasourceUrl}">${datasource.getTitle()?cap_first}</a></h2>
<form action="?" method="GET" property="hydra:search" resource="#triplePattern">
<fieldset resource="#triplePattern">
<legend>Query ${datasource.getTitle()} by triple pattern</legend>
<ul>
<#list ['subject', 'predicate', 'object'] as component>
<li property="hydra:mapping" resource="#${component}">
<label for="${component}>"
about="#${component}" property="hydra:variable" lang="">${component}</label>
<input class="uri" id="${component}" name="${component}"
about="#${component}" property="hydra:property" resource="rdf:${component}" value="${(query[component]?html)!""}" />
</li>
</#list>
</ul>
</fieldset>
<p>
<input type="submit" value="Find matching triples" />
</p>
</form>
</div>
<h3>Matches in ${datasource.getTitle()} for <em class="pattern">${ (query["pattern"]?html)!"" }</em></h3>
<div class="counts">
<#if (triples?size > 0)>
Showing triples ${ start } to ${ end } of
<#if totalEstimate != end>±</#if>
<span property="void:triples hydra:totalItems" datatype="xsd:integer" content="${ totalEstimate }">${ totalEstimate }</span>
with <span property="hydra:itemsPerPage" datatype="xsd:integer" content="${ itemsPerPage }">${
itemsPerPage
}</span> triples per page.
<@pageLinks/>
<#else>
<p>
${datasource.getTitle()} contains
<span property="void:triples hydra:totalItems" datatype="xsd:integer" content="0">
no <#if (totalEstimate > 0) >more</#if>
</span>
triples that match this pattern.
</p>
</#if>
</div>
<ul class="triples">
<#list triples as triple>
<#assign subject = triple.getSubject().asNode().toString()>
<#assign predicate = triple.getPredicate().asNode().toString()>
<#assign object = triple.getObject().asNode().toString()>
<li>
<a href="?subject=${subject?url}">
<abbr title="${ subject }">${subject?keep_after_last("/")}</abbr>
</a>
<a href="?predicate=${predicate?url}">
<abbr title="${ predicate }">${predicate?keep_after_last("/")}</abbr>
</a>
<#if !triple.getObject().isLiteral()>
<a href="?object=${object?url}" resource="${ subject}">
<abbr title="${ object }" property="${ predicate }" resource="${ object }">${object?keep_after_last("/")}</abbr>
</a>.
<#else>
<a href="?object=${object?url}" resource="${ subject}">${object}</a>.
</#if>
</li>
</#list>
</ul>
<@pageLinks/>
<#macro pageLinks>
<ul class="links">
<#if previousPage??>
<li><a href="${ firstPage }" rel="first" property="hydra:firstPage">first</a></li>
<li><a href="${ previousPage }" rel="prev" property="hydra:previousPage">previous</a></li>
</#if>
<#if nextPage??>
<li><a href="${ nextPage }" rel="next" property="hydra:nextPage">next</a></li>
</#if>
</ul>
</#macro>

View file

@ -0,0 +1,21 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#include "base.ftl.html">
<#macro contents>
<div class="index">
<h2>Available datasets</h2>
<p>Browse the following datasets as <a href="http://linkeddatafragments.org/in-depth/#tpf">Triple Pattern Fragments</a>:</p>
<dl class="datasets">
<#if datasources??>
<#list datasources?keys as datasourceName>
<dt><a href="${homePath}/${datasourceName}">${datasources[datasourceName].getTitle() }</a></dt>
<dd>${ datasources[datasourceName].getDescription()!"" }</dd>
</#list>
</#if>
</dl>
<p>The current dataset <em class="dataset">index</em> contains metadata about these datasets.</p>
</div>
<#include "fragment.ftl.html">
</#macro>
<@display_page/>

View file

@ -0,0 +1,16 @@
<#-- @license ©2015 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University -->
<#include "base.ftl.html">
<#macro contents>
<h2>Resource not found</h2>
<p>
No resource with URL <code>${ url!"" }</code> was found.
</p>
<h3>Available datasets</h3>
<ul>
<#list datasources?keys as datasourceName>
<li><a href="/${datasourceName}">${datasources[datasourceName].getTitle() }</a></li>
</#list>
</ul>
</#macro>
<@display_page/>

View file

@ -284,6 +284,11 @@
<artifactId>antisamy</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.5</version>
</dependency>
<!-- Used for JSP runtime -->
<!-- dependency>

View file

@ -1243,6 +1243,16 @@
<url-pattern>/selectLocale</url-pattern>
</servlet-mapping>
<servlet>
<display-name>TpfServlet</display-name>
<servlet-name>TpfServlet</servlet-name>
<servlet-class>org.vivoweb.linkeddatafragments.servlet.VitroLinkedDataFragmentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TpfServlet</servlet-name>
<url-pattern>/tpf/*</url-pattern>
</servlet-mapping>
<!-- ==================== mime types ============================== -->
<mime-mapping>