[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:
parent
e9cb3de52e
commit
68a462b05a
63 changed files with 5215 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
")";
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -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() + ")"; }
|
||||
}
|
||||
|
||||
}
|
|
@ -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(); }
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
")";
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
299
api/src/main/java/org/linkeddatafragments/util/MIMEParse.java
Normal file
299
api/src/main/java/org/linkeddatafragments/util/MIMEParse.java
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||
}
|
||||
}
|
30
api/src/main/resources/tpf/base.ftl.html
Normal file
30
api/src/main/resources/tpf/base.ftl.html
Normal 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>
|
7
api/src/main/resources/tpf/datasource.ftl.html
Normal file
7
api/src/main/resources/tpf/datasource.ftl.html
Normal 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/>
|
11
api/src/main/resources/tpf/error.ftl.html
Normal file
11
api/src/main/resources/tpf/error.ftl.html
Normal 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/>
|
83
api/src/main/resources/tpf/fragment.ftl.html
Normal file
83
api/src/main/resources/tpf/fragment.ftl.html
Normal 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>
|
21
api/src/main/resources/tpf/index.ftl.html
Normal file
21
api/src/main/resources/tpf/index.ftl.html
Normal 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/>
|
16
api/src/main/resources/tpf/notfound.ftl.html
Normal file
16
api/src/main/resources/tpf/notfound.ftl.html
Normal 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/>
|
5
dependencies/pom.xml
vendored
5
dependencies/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue