diff --git a/pom.xml b/pom.xml index 09a004f..f64f84d 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,12 @@ 4.0.0 LDF-Server LDF-Server - 0.0.1 + 0.0.2 war - 9.2.5.v20141112 + 9.3.6.v20151106 - org.rdfhdt @@ -31,10 +30,10 @@ httpclient 4.3.5 - + com.google.code.gson gson - 2.3 + 2.5 javax.servlet @@ -55,7 +54,7 @@ commons-cli commons-cli - 1.2 + 1.3.1 org.apache.commons @@ -116,4 +115,4 @@ - \ No newline at end of file + diff --git a/src/org/linkeddatafragments/config/ConfigReader.java b/src/org/linkeddatafragments/config/ConfigReader.java index 23aff12..fb982d9 100644 --- a/src/org/linkeddatafragments/config/ConfigReader.java +++ b/src/org/linkeddatafragments/config/ConfigReader.java @@ -15,9 +15,8 @@ * @author Ruben Verborgh */ public class ConfigReader { - - private final Map dataSources = new HashMap(); - private final Map prefixes = new HashMap(); + private final Map dataSources = new HashMap<>(); + private final Map prefixes = new HashMap<>(); /** * Creates a new configuration reader. @@ -25,12 +24,12 @@ public class ConfigReader { * @param configReader the configuration */ public ConfigReader(Reader configReader) { - final JsonObject root = new JsonParser().parse(configReader).getAsJsonObject(); - for (final Entry entry : root.getAsJsonObject("datasources").entrySet()) { - final JsonObject dataSource = entry.getValue().getAsJsonObject(); + JsonObject root = new JsonParser().parse(configReader).getAsJsonObject(); + for (Entry entry : root.getAsJsonObject("datasources").entrySet()) { + JsonObject dataSource = entry.getValue().getAsJsonObject(); this.dataSources.put(entry.getKey(), dataSource); } - for (final Entry entry : root.getAsJsonObject("prefixes").entrySet()) { + for (Entry entry : root.getAsJsonObject("prefixes").entrySet()) { this.prefixes.put(entry.getKey(), entry.getValue().getAsString()); } } diff --git a/src/org/linkeddatafragments/datasource/HdtDataSource.java b/src/org/linkeddatafragments/datasource/HdtDataSource.java index e1acf17..f40c302 100644 --- a/src/org/linkeddatafragments/datasource/HdtDataSource.java +++ b/src/org/linkeddatafragments/datasource/HdtDataSource.java @@ -49,53 +49,61 @@ public TriplePatternFragment getFragment(Resource subject, Property predicate, R throw new IllegalArgumentException("limit"); } - // look up the result from the HDT datasource - final int subjectId = subject == null ? 0 : dictionary.getIntID(subject.asNode(), TripleComponentRole.SUBJECT); - final int predicateId = predicate == null ? 0 : dictionary.getIntID(predicate.asNode(), TripleComponentRole.PREDICATE); - final int objectId = object == null ? 0 : dictionary.getIntID(object.asNode(), TripleComponentRole.OBJECT); + // look up the result from the HDT datasource) + int subjectId = subject == null ? 0 : dictionary.getIntID(subject.asNode(), TripleComponentRole.SUBJECT); + int predicateId = predicate == null ? 0 : dictionary.getIntID(predicate.asNode(), TripleComponentRole.PREDICATE); + int objectId = object == null ? 0 : dictionary.getIntID(object.asNode(), TripleComponentRole.OBJECT); + if (subjectId < 0 || predicateId < 0 || objectId < 0) { return new TriplePatternFragmentBase(); } + final Model triples = ModelFactory.createDefaultModel(); - final IteratorTripleID matches = datasource.getTriples().search(new TripleID(subjectId, predicateId, objectId)); - final boolean hasMatches = matches.hasNext(); + IteratorTripleID matches = datasource.getTriples().search(new TripleID(subjectId, predicateId, objectId)); + boolean hasMatches = matches.hasNext(); - if (hasMatches) { - // try to jump directly to the offset - boolean atOffset; - if (matches.canGoTo()) { - try { - matches.goTo(offset); - atOffset = true; - } - // if the offset is outside the bounds, this page has no matches - catch (IndexOutOfBoundsException exception) { atOffset = false; } - } - // if not possible, advance to the offset iteratively - else { - matches.goToStart(); - for (int i = 0; !(atOffset = i == offset) && matches.hasNext(); i++) - matches.next(); - } - // try to add `limit` triples to the result model - if (atOffset) { - for (int i = 0; i < limit && matches.hasNext(); i++) - triples.add(triples.asStatement(toTriple(matches.next()))); - } - } - - // estimates can be wrong; ensure 0 is returned if there are no results, and always more than actual results - final long estimatedTotal = triples.size() > 0 ? Math.max(offset + triples.size() + 1, matches.estimatedNumResults()) - : hasMatches ? Math.max(matches.estimatedNumResults(), 1) : 0; + if (hasMatches) { + // try to jump directly to the offset + boolean atOffset; + if (matches.canGoTo()) { + try { + matches.goTo(offset); + atOffset = true; + } // if the offset is outside the bounds, this page has no matches + catch (IndexOutOfBoundsException exception) { + atOffset = false; + } + } // if not possible, advance to the offset iteratively + else { + matches.goToStart(); + for (int i = 0; !(atOffset = i == offset) && matches.hasNext(); i++) { + matches.next(); + } + } + // try to add `limit` triples to the result model + if (atOffset) { + for (int i = 0; i < limit && matches.hasNext(); i++) { + triples.add(triples.asStatement(toTriple(matches.next()))); + } + } + } + + // estimates can be wrong; ensure 0 is returned if there are no results, and always more than actual results + final long estimatedTotal = triples.size() > 0 ? Math.max(offset + triples.size() + 1, matches.estimatedNumResults()) + : hasMatches ? Math.max(matches.estimatedNumResults(), 1) : 0; // create the fragment return new TriplePatternFragment() { - @Override - public Model getTriples() { return triples; } - - @Override - public long getTotalSize() { return estimatedTotal; } - }; + @Override + public Model getTriples() { + return triples; + } + + @Override + public long getTotalSize() { + return estimatedTotal; + } + }; } /** @@ -106,9 +114,9 @@ public TriplePatternFragment getFragment(Resource subject, Property predicate, R */ private Triple toTriple(TripleID tripleId) { return new Triple( - dictionary.getNode(tripleId.getSubject(), TripleComponentRole.SUBJECT), - dictionary.getNode(tripleId.getPredicate(), TripleComponentRole.PREDICATE), - dictionary.getNode(tripleId.getObject(), TripleComponentRole.OBJECT) + dictionary.getNode(tripleId.getSubject(), TripleComponentRole.SUBJECT), + dictionary.getNode(tripleId.getPredicate(), TripleComponentRole.PREDICATE), + dictionary.getNode(tripleId.getObject(), TripleComponentRole.OBJECT) ); } } diff --git a/src/org/linkeddatafragments/datasource/IDataSource.java b/src/org/linkeddatafragments/datasource/IDataSource.java index 47cc180..2b5a24d 100644 --- a/src/org/linkeddatafragments/datasource/IDataSource.java +++ b/src/org/linkeddatafragments/datasource/IDataSource.java @@ -9,18 +9,18 @@ * @author Ruben Verborgh */ public interface IDataSource { - /** - * Gets a page of the Basic Linked Data Fragment matching the specified triple pattern. - * @param subject the subject (null to match any subject) - * @param predicate the predicate (null to match any predicate) - * @param object the object (null to match any object) - * @param offset the triple index at which to start the page - * @param limit the number of triples on the page - * @return the first page of the fragment - */ - public TriplePatternFragment getFragment(Resource subject, Property predicate, RDFNode object, - long offset, long limit); - public String getTitle(); + /** + * Gets a page of the Basic Linked Data Fragment matching the specified triple pattern. + * @param subject the subject (null to match any subject) + * @param predicate the predicate (null to match any predicate) + * @param object the object (null to match any object) + * @param offset the triple index at which to start the page + * @param limit the number of triples on the page + * @return the first page of the fragment + */ + public TriplePatternFragment getFragment(Resource subject, Property predicate, + RDFNode object, long offset, long limit); + public String getTitle(); - public String getDescription(); + public String getDescription(); } diff --git a/src/org/linkeddatafragments/datasource/TriplePatternFragment.java b/src/org/linkeddatafragments/datasource/TriplePatternFragment.java index 02cac35..fa21d36 100644 --- a/src/org/linkeddatafragments/datasource/TriplePatternFragment.java +++ b/src/org/linkeddatafragments/datasource/TriplePatternFragment.java @@ -7,15 +7,15 @@ * @author Ruben Verborgh */ public interface TriplePatternFragment { - /** - * Gets the data of this fragment (possibly only partial). - * @return the data as triples - */ - public Model getTriples(); - - /** - * Gets the total number of triples in the fragment (can be an estimate). - * @return the total number of triples - */ - public long getTotalSize(); + /** + * Gets the data of this fragment (possibly only partial). + * @return the data as triples + */ + public Model getTriples(); + + /** + * Gets the total number of triples in the fragment (can be an estimate). + * @return the total number of triples + */ + public long getTotalSize(); } diff --git a/src/org/linkeddatafragments/datasource/TriplePatternFragmentBase.java b/src/org/linkeddatafragments/datasource/TriplePatternFragmentBase.java index b7ac88d..3645b08 100644 --- a/src/org/linkeddatafragments/datasource/TriplePatternFragmentBase.java +++ b/src/org/linkeddatafragments/datasource/TriplePatternFragmentBase.java @@ -8,33 +8,33 @@ * @author Ruben Verborgh */ public class TriplePatternFragmentBase implements TriplePatternFragment { - private final Model triples; - private final long totalSize; - - /** - * Creates an empty Basic Linked Data Fragment. - */ - public TriplePatternFragmentBase() { - this(null, 0); - } - - /** - * Creates a new Basic Linked Data Fragment. - * @param triples the triples (possibly partial) - * @param totalSize the total size - */ - public TriplePatternFragmentBase(Model triples, long totalSize) { - this.triples = triples == null ? ModelFactory.createDefaultModel() : triples; - this.totalSize = totalSize < 0 ? 0 : totalSize; - } + private final Model triples; + private final long totalSize; - @Override - public Model getTriples() { - return triples; - } + /** + * Creates an empty Basic Linked Data Fragment. + */ + public TriplePatternFragmentBase() { + this(null, 0); + } - @Override - public long getTotalSize() { - return totalSize; - } + /** + * Creates a new Basic Linked Data Fragment. + * @param triples the triples (possibly partial) + * @param totalSize the total size + */ + public TriplePatternFragmentBase(Model triples, long totalSize) { + this.triples = triples == null ? ModelFactory.createDefaultModel() : triples; + this.totalSize = totalSize < 0 ? 0 : totalSize; + } + + @Override + public Model getTriples() { + return triples; + } + + @Override + public long getTotalSize() { + return totalSize; + } } diff --git a/src/org/linkeddatafragments/exceptions/DataSourceException.java b/src/org/linkeddatafragments/exceptions/DataSourceException.java index a82799e..fb6bd6a 100644 --- a/src/org/linkeddatafragments/exceptions/DataSourceException.java +++ b/src/org/linkeddatafragments/exceptions/DataSourceException.java @@ -5,6 +5,7 @@ * @author mielvandersande */ public class DataSourceException extends Exception { + private static final long serialVersionUID = 1L; public DataSourceException(Throwable cause) { super(cause.getMessage()); @@ -12,6 +13,5 @@ public DataSourceException(Throwable cause) { public DataSourceException(String message) { super("Could not create DataSource: " + message); - } - + } } diff --git a/src/org/linkeddatafragments/exceptions/UnknownDataSourceTypeException.java b/src/org/linkeddatafragments/exceptions/UnknownDataSourceTypeException.java index e5bd95d..e7dc9fc 100644 --- a/src/org/linkeddatafragments/exceptions/UnknownDataSourceTypeException.java +++ b/src/org/linkeddatafragments/exceptions/UnknownDataSourceTypeException.java @@ -5,9 +5,9 @@ * @author mielvandersande */ public class UnknownDataSourceTypeException extends DataSourceException { + private static final long serialVersionUID = 1L; public UnknownDataSourceTypeException(String type) { super("Type " + type + " does not exist."); - } - + } } diff --git a/src/org/linkeddatafragments/servlet/TriplePatternFragmentServlet.java b/src/org/linkeddatafragments/servlet/TriplePatternFragmentServlet.java index 9764cde..f86d927 100644 --- a/src/org/linkeddatafragments/servlet/TriplePatternFragmentServlet.java +++ b/src/org/linkeddatafragments/servlet/TriplePatternFragmentServlet.java @@ -1,120 +1,262 @@ package org.linkeddatafragments.servlet; -import com.google.gson.JsonObject; -import com.hp.hpl.jena.datatypes.TypeMapper; -import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.ResourceFactory; -import com.hp.hpl.jena.shared.InvalidPropertyURIException; import java.io.File; import java.io.FileReader; +import java.io.IOException; + +import java.net.URISyntaxException; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.codec.CharEncoding; + +import org.apache.http.HttpHeaders; import org.apache.http.client.utils.URIBuilder; + import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFDataMgr; import org.apache.jena.riot.RDFLanguages; + +import com.google.gson.JsonObject; + +import com.hp.hpl.jena.datatypes.TypeMapper; +import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; +import com.hp.hpl.jena.shared.InvalidPropertyURIException; + import org.linkeddatafragments.config.ConfigReader; import org.linkeddatafragments.datasource.DataSourceFactory; import org.linkeddatafragments.datasource.IDataSource; import org.linkeddatafragments.datasource.TriplePatternFragment; - -import static org.linkeddatafragments.util.CommonResources.*; - +import org.linkeddatafragments.exceptions.DataSourceException; +import org.linkeddatafragments.util.CommonResources; import org.linkeddatafragments.util.MIMEParse; /** * Servlet that responds with a Basic Linked Data Fragment. * * @author Ruben Verborgh + * @author Bart Hanssens */ public class TriplePatternFragmentServlet extends HttpServlet { - private final static long serialVersionUID = 1L; - private final static Pattern STRINGPATTERN = Pattern.compile("^\"(.*)\"(?:@(.*)|\\^\\^]*)>?)?$"); - private final static TypeMapper types = TypeMapper.getInstance(); + + // Parameters + public final static String CFGFILE = "configFile"; + public final static String SUBJ = "subject"; + public final static String PRED = "predicate"; + public final static String OBJ = "object"; + public final static String PAGE = "page"; + + + private final static Pattern STRINGPATTERN = + Pattern.compile("^\"(.*)\"(?:@(.*)|\\^\\^]*)>?)?$"); + private final static TypeMapper TYPES = TypeMapper.getInstance(); private final static long TRIPLESPERPAGE = 100; private ConfigReader config; private final HashMap dataSources = new HashMap<>(); private final Collection 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; + } + @Override public void init(ServletConfig servletConfig) throws ServletException { try { - // find the configuration file - String applicationPathStr = servletConfig.getServletContext().getRealPath("/"); - if (applicationPathStr == null) { // this can happen when running standalone - applicationPathStr = System.getProperty("user.dir"); - } - final File applicationPath = new File(applicationPathStr); - - File configFile = new File(applicationPath, "config-example.json"); - if (servletConfig.getInitParameter("configFile") != null) { - configFile = new File(servletConfig.getInitParameter("configFile")); - } - - if (!configFile.exists()) { - throw new Exception("Configuration file " + configFile + " not found."); - } - - if (!configFile.isFile()) { - throw new Exception("Configuration file " + configFile + " is not a file."); - } - // load the configuration + File configFile = getConfigFile(servletConfig); config = new ConfigReader(new FileReader(configFile)); + for (Entry dataSource : config.getDataSources().entrySet()) { dataSources.put(dataSource.getKey(), DataSourceFactory.create(dataSource.getValue())); } - - // register content types + // register content types mimeTypes.add(Lang.TTL.getHeaderString()); mimeTypes.add(Lang.JSONLD.getHeaderString()); mimeTypes.add(Lang.NTRIPLES.getHeaderString()); mimeTypes.add(Lang.RDFXML.getHeaderString() ); - } catch (Exception e) { + } catch (IOException | DataSourceException e) { throw new ServletException(e); } } + /** + * Get the datasource + * + * @param request + * @return + * @throws IOException + */ + private IDataSource getDataSource(HttpServletRequest request) throws IOException { + String contextPath = request.getContextPath(); + String requestURI = request.getRequestURI(); + + String path = contextPath == null + ? requestURI + : requestURI.substring(contextPath.length()); + String dataSourceName = path.substring(1); + IDataSource dataSource = dataSources.get(dataSourceName); + if (dataSource == null) { + throw new IOException("Data source not found."); + } + return dataSource; + } + + /** + * Get dataset url + * + * @param request + * @return + */ + private String getDatasetUrl(HttpServletRequest request) { + String hostName = request.getHeader(HttpHeaders.SERVER); + if (hostName == null) { + hostName = request.getServerName(); + } + return request.getScheme() + "://" + hostName + request.getRequestURI(); + } + + /** + * Add total and limit + * + * @param output + * @param fragmentId + * @param total + * @param limit + */ + private void addMeta(Model output, Resource datasetId, Resource fragmentId, + long total, long limit) { + output.add(datasetId, CommonResources.RDF_TYPE, CommonResources.VOID_DATASET); + output.add(datasetId, CommonResources.RDF_TYPE, CommonResources.HYDRA_COLLECTION); + output.add(datasetId, CommonResources.VOID_SUBSET, fragmentId); + + output.add(fragmentId, CommonResources.RDF_TYPE, CommonResources.HYDRA_COLLECTION); + output.add(fragmentId, CommonResources.RDF_TYPE, CommonResources.HYDRA_PAGEDCOLLECTION); + + Literal totalTyped = output.createTypedLiteral(total, XSDDatatype.XSDinteger); + Literal limitTyped = output.createTypedLiteral(limit, XSDDatatype.XSDinteger); + + output.add(fragmentId, CommonResources.VOID_TRIPLES, totalTyped); + output.add(fragmentId, CommonResources.HYDRA_TOTALITEMS, totalTyped); + output.add(fragmentId, CommonResources.HYDRA_ITEMSPERPAGE, limitTyped); + } + + + /** + * Add reference to first/previous/next page + * + * @param output + * @param fragmentId + * @param fragmentUrl + * @param total + * @param limit + * @param offset + * @param page + * @throws URISyntaxException + */ + private void addPages(Model output, Resource fragmentId, String fragmentUrl, + long total, long limit, long offset, long page) throws URISyntaxException { + URIBuilder pagedUrl = new URIBuilder(fragmentUrl); + + pagedUrl.setParameter(PAGE, "1"); + output.add(fragmentId, CommonResources.HYDRA_FIRSTPAGE, + output.createResource(pagedUrl.toString())); + if (offset > 0) { + pagedUrl.setParameter(PAGE, Long.toString(page - 1)); + output.add(fragmentId, CommonResources.HYDRA_PREVIOUSPAGE, + output.createResource(pagedUrl.toString())); + } + if (offset + limit < total) { + pagedUrl.setParameter(PAGE, Long.toString(page + 1)); + output.add(fragmentId, CommonResources.HYDRA_NEXTPAGE, + output.createResource(pagedUrl.toString())); + } + } + + /** + * Add controls to output + * + * @param output + * @param datasetId + * @param datasetUrl + */ + private void addControls(Model output, Resource datasetId, String datasetUrl) { + // add controls + Resource triplePattern = output.createResource(); + Resource subjectMapping = output.createResource(); + Resource predicateMapping = output.createResource(); + Resource objectMapping = output.createResource(); + + output.add(datasetId, CommonResources.HYDRA_SEARCH, triplePattern); + output.add(triplePattern, CommonResources.HYDRA_TEMPLATE, output.createLiteral(datasetUrl + "{?subject,predicate,object}")); + output.add(triplePattern, CommonResources.HYDRA_MAPPING, subjectMapping); + output.add(triplePattern, CommonResources.HYDRA_MAPPING, predicateMapping); + output.add(triplePattern, CommonResources.HYDRA_MAPPING, objectMapping); + + output.add(subjectMapping, CommonResources.HYDRA_VARIABLE, output.createLiteral(SUBJ)); + output.add(subjectMapping, CommonResources.HYDRA_PROPERTY, CommonResources.RDF_SUBJECT); + + output.add(predicateMapping, CommonResources.HYDRA_VARIABLE, output.createLiteral(PRED)); + output.add(predicateMapping, CommonResources.HYDRA_PROPERTY, CommonResources.RDF_PREDICATE); + output.add(objectMapping, CommonResources.HYDRA_VARIABLE, output.createLiteral(OBJ)); + + output.add(objectMapping, CommonResources.HYDRA_PROPERTY, CommonResources.RDF_OBJECT); + } + + @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { - // find the data source - final String contextPath = request.getContextPath(); - final String requestURI = request.getRequestURI(); - final String path = contextPath == null ? requestURI : requestURI.substring(contextPath.length()); - final String query = request.getQueryString(); - final String dataSourceName = path.substring(1); - final IDataSource dataSource = dataSources.get(dataSourceName); - if (dataSource == null) { - throw new Exception("Data source not found."); - } - + IDataSource dataSource = getDataSource(request); + // query the fragment - final Resource subject = parseAsResource(request.getParameter("subject")); - final Property predicate = parseAsProperty(request.getParameter("predicate")); - final RDFNode object = parseAsNode(request.getParameter("object")); - final long page = Math.max(1, parseAsInteger(request.getParameter("page"))); - final long limit = TRIPLESPERPAGE, offset = limit * (page - 1); - final TriplePatternFragment fragment = dataSource.getFragment(subject, predicate, object, offset, limit); + Resource subject = parseAsResource(request.getParameter(SUBJ)); + Property predicate = parseAsProperty(request.getParameter(PRED)); + RDFNode object = parseAsNode(request.getParameter(OBJ)); + + long page = Math.max(1, parseAsInteger(request.getParameter(PAGE))); + long limit = TRIPLESPERPAGE; + long offset = limit * (page - 1); + + TriplePatternFragment fragment = + dataSource.getFragment(subject, predicate, object, offset, limit); // fill the output model - final Model output = fragment.getTriples(); + Model output = fragment.getTriples(); output.setNsPrefixes(config.getPrefixes()); // do conneg @@ -128,54 +270,26 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro RDFDataMgr.write(response.getOutputStream(), output, contentType); // add dataset metadata - final String hostName = request.getHeader("Host"); - final String datasetUrl = request.getScheme() + "://" - + (hostName == null ? request.getServerName() : hostName) + request.getRequestURI(); - final String fragmentUrl = query == null ? datasetUrl : (datasetUrl + "?" + query); - final Resource datasetId = output.createResource(datasetUrl + "#dataset"); - final Resource fragmentId = output.createResource(fragmentUrl); - output.add(datasetId, RDF_TYPE, VOID_DATASET); - output.add(datasetId, RDF_TYPE, HYDRA_COLLECTION); - output.add(datasetId, VOID_SUBSET, fragmentId); - - // add fragment metadata - output.add(fragmentId, RDF_TYPE, HYDRA_COLLECTION); - output.add(fragmentId, RDF_TYPE, HYDRA_PAGEDCOLLECTION); - final Literal total = output.createTypedLiteral(fragment.getTotalSize(), XSDDatatype.XSDinteger); - output.add(fragmentId, VOID_TRIPLES, total); - output.add(fragmentId, HYDRA_TOTALITEMS, total); - output.add(fragmentId, HYDRA_ITEMSPERPAGE, output.createTypedLiteral(limit, XSDDatatype.XSDinteger)); - - // add pages - final URIBuilder pagedUrl = new URIBuilder(fragmentUrl); - pagedUrl.setParameter("page", "1"); - output.add(fragmentId, HYDRA_FIRSTPAGE, output.createResource(pagedUrl.toString())); - if (offset > 0) { - pagedUrl.setParameter("page", Long.toString(page - 1)); - output.add(fragmentId, HYDRA_PREVIOUSPAGE, output.createResource(pagedUrl.toString())); - } - if (offset + limit < fragment.getTotalSize()) { - pagedUrl.setParameter("page", Long.toString(page + 1)); - output.add(fragmentId, HYDRA_NEXTPAGE, output.createResource(pagedUrl.toString())); - } + String datasetUrl = getDatasetUrl(request); + Resource datasetId = output.createResource(datasetUrl + "#dataset"); + + String query = request.getQueryString(); + String fragmentUrl = query == null ? datasetUrl : (datasetUrl + "?" + query); + Resource fragmentId = output.createResource(fragmentUrl); - // add controls - final Resource triplePattern = output.createResource(); - final Resource subjectMapping = output.createResource(); - final Resource predicateMapping = output.createResource(); - final Resource objectMapping = output.createResource(); - output.add(datasetId, HYDRA_SEARCH, triplePattern); - output.add(triplePattern, HYDRA_TEMPLATE, output.createLiteral(datasetUrl + "{?subject,predicate,object}")); - output.add(triplePattern, HYDRA_MAPPING, subjectMapping); - output.add(triplePattern, HYDRA_MAPPING, predicateMapping); - output.add(triplePattern, HYDRA_MAPPING, objectMapping); - output.add(subjectMapping, HYDRA_VARIABLE, output.createLiteral("subject")); - output.add(subjectMapping, HYDRA_PROPERTY, RDF_SUBJECT); - output.add(predicateMapping, HYDRA_VARIABLE, output.createLiteral("predicate")); - output.add(predicateMapping, HYDRA_PROPERTY, RDF_PREDICATE); - output.add(objectMapping, HYDRA_VARIABLE, output.createLiteral("object")); - output.add(objectMapping, HYDRA_PROPERTY, RDF_OBJECT); - } catch (Exception e) { + long total = fragment.getTotalSize(); + + addMeta(output, datasetId, fragmentId, total, limit); + addPages(output, fragmentId, fragmentUrl, total, limit, offset, page); + addControls(output, datasetId, datasetUrl); + + // serialize the output as Turtle + response.setHeader(HttpHeaders.SERVER, "Linked Data Fragments Server"); + response.setContentType("text/turtle"); + response.setCharacterEncoding(CharEncoding.UTF_8); + + output.write(response.getWriter(), "Turtle", fragmentUrl); + } catch (IOException | URISyntaxException e) { throw new ServletException(e); } } @@ -201,8 +315,10 @@ private int parseAsInteger(String value) { * @return the parsed value, or null if unspecified */ private Resource parseAsResource(String value) { - final RDFNode subject = parseAsNode(value); - return subject == null || subject instanceof Resource ? (Resource) subject : INVALID_URI; + RDFNode subject = parseAsNode(value); + return subject == null || subject instanceof Resource + ? (Resource) subject + : CommonResources.INVALID_URI; } /** @@ -212,15 +328,15 @@ private Resource parseAsResource(String value) { * @return the parsed value, or null if unspecified */ private Property parseAsProperty(String value) { - final RDFNode predicateNode = parseAsNode(value); + RDFNode predicateNode = parseAsNode(value); if (predicateNode instanceof Resource) { try { return ResourceFactory.createProperty(((Resource) predicateNode).getURI()); } catch (InvalidPropertyURIException ex) { - return INVALID_URI; + return CommonResources.INVALID_URI; } } - return predicateNode == null ? null : INVALID_URI; + return predicateNode == null ? null : CommonResources.INVALID_URI; } /** @@ -231,11 +347,11 @@ private Property parseAsProperty(String value) { */ private RDFNode parseAsNode(String value) { // nothing or empty indicates an unknown - if (value == null || value.length() == 0) { + if (value == null || value.isEmpty()) { return null; } // find the kind of entity based on the first character - final char firstChar = value.charAt(0); + char firstChar = value.charAt(0); switch (firstChar) { // variable or blank node indicates an unknown case '?': @@ -246,20 +362,20 @@ private RDFNode parseAsNode(String value) { return ResourceFactory.createResource(value.substring(1, value.length() - 1)); // quotes indicate a string case '"': - final Matcher matcher = STRINGPATTERN.matcher(value); + Matcher matcher = STRINGPATTERN.matcher(value); if (matcher.matches()) { - final String body = matcher.group(1); - final String lang = matcher.group(2); - final String type = matcher.group(3); + String body = matcher.group(1); + String lang = matcher.group(2); + String type = matcher.group(3); if (lang != null) { return ResourceFactory.createLangLiteral(body, lang); } if (type != null) { - return ResourceFactory.createTypedLiteral(body, types.getSafeTypeByName(type)); + return ResourceFactory.createTypedLiteral(body, TYPES.getSafeTypeByName(type)); } return ResourceFactory.createPlainLiteral(body); } - return INVALID_URI; + return CommonResources.INVALID_URI; // assume it's a URI without angular brackets default: return ResourceFactory.createResource(value); diff --git a/src/org/linkeddatafragments/standalone/JettyServer.java b/src/org/linkeddatafragments/standalone/JettyServer.java index 6d59d07..23ff9c1 100644 --- a/src/org/linkeddatafragments/standalone/JettyServer.java +++ b/src/org/linkeddatafragments/standalone/JettyServer.java @@ -1,9 +1,15 @@ package org.linkeddatafragments.standalone; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; + import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; + import org.linkeddatafragments.servlet.TriplePatternFragmentServlet; /** @@ -16,64 +22,58 @@ * need for a separate servlet container such as Tomcat.

* *

- * Copyright 2014 MMLab, UGent * * @author Gerald Haesendonck * @author Miel Vander Sande + * @author Bart Hanssens */ public class JettyServer { - + private static void printHelp(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(JettyServer.class.getName() + " [config-example.json] []", + "Starts a standalone LDF Triple Pattern server. Options:", options, ""); + } + public static void main(String[] args) throws Exception { Options options = new Options(); options.addOption("h", "help", false, "Print this help message and then exit."); options.addOption("p", "port", true, "The port the server listents to. The default is 8080."); - boolean printHelp = false; - CommandLineParser parser = new BasicParser(); - try { - CommandLine commandLine = parser.parse(options, args); + + CommandLineParser parser = new DefaultParser(); + CommandLine commandLine = parser.parse(options, args); - String config = null; - if (!commandLine.getArgList().isEmpty()) { - config = commandLine.getArgs()[0]; - } - - if (commandLine.hasOption('h')) { - printHelp = true; - return; - } - int port; - if (commandLine.hasOption('p')) { - port = Integer.parseInt(commandLine.getOptionValue('p')); - } else { - port = 8080; - } + String config = null; + if (!commandLine.getArgList().isEmpty()) { + config = commandLine.getArgs()[0]; + } - // create a new (Jetty) server, and add a servlet handler - Server server = new Server(port); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); + if (config == null || commandLine.hasOption('h')) { + printHelp(options); + System.exit(-1); + } + + int port = 8080; + if (commandLine.hasOption('p')) { + port = Integer.parseInt(commandLine.getOptionValue('p')); + } - // add the TriplePatternFragmentsServlet to the handler - ServletHolder tpfServletHolder = new ServletHolder(new TriplePatternFragmentServlet()); - tpfServletHolder.setInitParameter("configFile", config); - handler.addServletWithMapping(tpfServletHolder, "/*"); + // create a new (Jetty) server, and add a servlet handler + Server server = new Server(port); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); - // start the server - server.start(); - System.out.println("Started server, listening at port " + port); + // add the TriplePatternFragmentsServlet to the handler + ServletHolder tpfServletHolder = new ServletHolder(new TriplePatternFragmentServlet()); + tpfServletHolder.setInitParameter(TriplePatternFragmentServlet.CFGFILE, config); + handler.addServletWithMapping(tpfServletHolder, "/*"); - // The use of server.join() the will make the current thread join and wait until the server is done executing. - // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() - server.join(); - } catch (ParseException e) { - System.out.println(e.getMessage()); - printHelp = true; + // start the server + server.start(); + System.out.println("Started server, listening at port " + port); - } finally { - if (printHelp) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp(JettyServer.class.getName() + " [config-example.json] []", "Starts a standalone LDF Trpile Pattern server. Options:", options, ""); - } - } + // The use of server.join() the will make the current thread join and wait until the server is done executing. + // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() + server.join(); } } diff --git a/src/org/linkeddatafragments/util/CommonResources.java b/src/org/linkeddatafragments/util/CommonResources.java index f7b4f2c..150ca17 100644 --- a/src/org/linkeddatafragments/util/CommonResources.java +++ b/src/org/linkeddatafragments/util/CommonResources.java @@ -5,35 +5,35 @@ @SuppressWarnings("javadoc") 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); - } + 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); + } }