[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>
|
<artifactId>antisamy</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Used for JSP runtime -->
|
<!-- Used for JSP runtime -->
|
||||||
<!-- dependency>
|
<!-- dependency>
|
||||||
|
|
|
@ -1243,6 +1243,16 @@
|
||||||
<url-pattern>/selectLocale</url-pattern>
|
<url-pattern>/selectLocale</url-pattern>
|
||||||
</servlet-mapping>
|
</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 types ============================== -->
|
||||||
|
|
||||||
<mime-mapping>
|
<mime-mapping>
|
||||||
|
|
Loading…
Add table
Reference in a new issue