/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.marmotta.platform.ldpath.webservices;
import static org.apache.marmotta.commons.sesame.repository.ResultUtils.iterable;
import static org.apache.marmotta.commons.sesame.repository.ExceptionUtils.handleRepositoryException;
import org.apache.commons.io.IOUtils;
import org.apache.marmotta.platform.ldpath.api.LDPathService;
import org.apache.marmotta.commons.sesame.repository.ResourceUtils;
import org.apache.marmotta.commons.util.JSONUtils;
import org.apache.marmotta.platform.core.api.triplestore.SesameService;
import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
import org.apache.marmotta.kiwi.model.rdf.KiWiStringLiteral;
import org.apache.marmotta.ldpath.api.functions.SelectorFunction;
import org.apache.marmotta.ldpath.backend.sesame.SesameConnectionBackend;
import org.apache.marmotta.ldpath.exception.LDPathParseException;
import org.openrdf.model.Namespace;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.slf4j.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.text.Collator;
import java.util.*;
/**
* Execute LDPath queries against the LMF backend. Depending on the LMF configuration, this might trigger retrieval
* of external Linked Data resources before returning results.
*
* <p/>
* Author: Sebastian Schaffert
*/
@ApplicationScoped
@Path("/ldpath")
public class LDPathWebService {
@Inject
private Logger log;
@Inject
private LDPathService ldPathService;
@Inject
private SesameService sesameService;
/**
* Evaluate a single LDPath selection using the path passed as argument and starting at the resource identified
* by the uri. Returns a list of RDF Nodes using the same syntax as RDF/JSON, i.e.
* <ul>
* <li> <code>{ "type": "uri", "value": "..." }</code> for resources</li>
* <li><code>{ "type": "literal", "value": "...", "language": "...", "datatype": "..."}</code> for literals (datatype and language optional)</li>
* </ul>
*
* @param path the LDPath expression to evaluate
* @param resourceUri the URI of the resource from which to start the resource
*
* @return a list of RDF Nodes using the same syntax as RDF/JSON
* @HTTP 404 in case the resource with the given URI does not exist
* @HTTP 400 in case the path could not be parsed or the resource is not a valid URI
* @HTTP 200 in case the query was evaluated successfully
*/
@GET
@Path("/path")
@Produces("application/json")
public Response evaluatePathQuery(@QueryParam("path") String path, @QueryParam("uri") String resourceUri) {
//Preconditions.checkArgument(urlValidator.isValid(resourceUri));
try {
RepositoryConnection con = sesameService.getConnection();
try {
con.begin();
if (ResourceUtils.isSubject(con, resourceUri)) {
URI resource = con.getValueFactory().createURI(resourceUri);
// get list of configured namespaces; we make them available for the path language
Map<String,String> namespaces = new HashMap<String, String>();
for(Namespace ns : iterable(con.getNamespaces())) {
namespaces.put(ns.getPrefix(),ns.getName());
}
List<Map<String,String>> result = new ArrayList<Map<String, String>>();
try {
for(Value node : ldPathService.pathQuery(resource,path,namespaces)) {
result.add(JSONUtils.serializeNodeAsJson(node));
}
return Response.ok().entity(result).build();
} catch (LDPathParseException e) {
log.warn("parse error while evaluating path {}: {}",path,e.getMessage());
return Response.status(Response.Status.BAD_REQUEST).entity("parse error while evaluating path '"+path+"': "+e.getMessage()).build();
}
} else
return Response.status(Response.Status.NOT_FOUND).entity("resource "+resourceUri+" does not exist").build();
} finally {
con.commit();
con.close();
}
} catch (RepositoryException ex) {
handleRepositoryException(ex,LDPathWebService.class);
return Response.serverError().entity("error accessing RDF repository: "+ex.getMessage()).build();
}
}
/**
* Evaluate a LDPath program using the program string passed as argument and starting at the resource identified
* by the uri. Returns a map from field names to lists of RDF nodes using the same syntax as RDF/JSON, i.e.
* <ul>
* <li> <code>{ "type": "uri", "value": "..." }</code> for resources</li>
* <li><code>{ "type": "literal", "value": "...", "language": "...", "datatype": "..."}</code> for literals (datatype and language optional)</li>
* </ul>
*
* @param program the program to evaluate
* @param resourceUri the URI of the resource where to start
* @return a map from field names to lists of rdf nodes in rdf/json format
* @HTTP 404 in case the resource with the given URI does not exist
* @HTTP 400 in case the path could not be parsed or the resource is not a valid URI
* @HTTP 200 in case the query was evaluated successfully
*/
@GET
@Path("/program")
@Produces("application/json")
public Response evaluateProgramQuery(@QueryParam("program") String program, @QueryParam("uri") String resourceUri) {
//Preconditions.checkArgument(urlValidator.isValid(resourceUri));
try {
RepositoryConnection con = sesameService.getConnection();
try {
con.begin();
if (ResourceUtils.isSubject(con, resourceUri)) {
URI resource = con.getValueFactory().createURI(resourceUri);
// get list of configured namespaces; we make them available for the path language
Map<String,String> namespaces = new HashMap<String, String>();
for(Namespace ns : iterable(con.getNamespaces())) {
namespaces.put(ns.getPrefix(),ns.getName());
}
Map<String,List<Map<String,String>>> result = new HashMap<String, List<Map<String, String>>>();
try {
for(Map.Entry<String,Collection<?>> row : ldPathService.programQuery(resource,program).entrySet()) {
List<Map<String,String>> rowList = new ArrayList<Map<String, String>>();
for(Object o : row.getValue()) {
if(o instanceof KiWiNode) {
rowList.add(JSONUtils.serializeNodeAsJson((Value) o));
} else {
// we convert always to a literal
rowList.add(JSONUtils.serializeNodeAsJson(new KiWiStringLiteral(o.toString())));
}
}
result.put(row.getKey(),rowList);
}
return Response.ok().entity(result).build();
} catch (LDPathParseException e) {
log.warn("parse error while evaluating program {}: {}", program, e.getMessage());
return Response.status(Response.Status.BAD_REQUEST).entity("parse error while evaluating program: "+e.getMessage()).build();
}
} else
return Response.status(Response.Status.NOT_FOUND).entity("resource "+resourceUri+" does not exist").build();
} finally {
con.commit();
con.close();
}
} catch (RepositoryException ex) {
handleRepositoryException(ex,LDPathWebService.class);
return Response.serverError().entity("error accessing RDF repository: "+ex.getMessage()).build();
}
}
/**
* Return a list of all LDPath functions that have been registered in the LDPath installation.
*
* @HTTP 200 in case the functions exist; will return the function descriptions
* @HTTP 500 in case there was an error accessing the triple store
*
* @return a list of JSON maps with the fields "name", "signature" and "description"
*/
@GET
@Path("/functions")
@Produces("application/json")
public Response listFunctions() {
List<Map<String,String>> results = new ArrayList<Map<String, String>>();
try {
RepositoryConnection con = sesameService.getConnection();
try {
con.begin();
SesameConnectionBackend backend = SesameConnectionBackend.withConnection(con);
for(SelectorFunction<Value> function : ldPathService.getFunctions()) {
Map<String,String> fmap = new HashMap<String, String>();
fmap.put("name", function.getPathExpression(backend));
fmap.put("signature",function.getSignature());
fmap.put("description",function.getDescription());
results.add(fmap);
}
} finally {
con.commit();
con.close();
}
} catch (RepositoryException e) {
return Response.serverError().entity(e).build();
}
Collections.sort(results, new Comparator<Map<String, String>>() {
@Override
public int compare(Map<String, String> o1, Map<String, String> o2) {
return Collator.getInstance().compare(o1.get("name"),o2.get("name"));
}
});
return Response.ok().entity(results).build();
}
/**
* Return a description of the function whose name is passed as path argument.
*
* @HTTP 200 in case the function exists; will return the function description
* @HTTP 404 in case the function does not exist
* @HTTP 500 in case there was an error accessing the triple store
*
* @return a JSON map with the fields "name", "signature" and "description"
*/
@GET
@Path("/functions/{name}")
@Produces("application/json")
public Response getFunction(@PathParam("name") String name) {
try {
RepositoryConnection con = sesameService.getConnection();
try {
con.begin();
SesameConnectionBackend backend = SesameConnectionBackend.withConnection(con);
for (SelectorFunction<Value> function : ldPathService.getFunctions()) {
final String fName = function.getPathExpression(backend);
if (name.equals(fName)) {
Map<String, String> fmap = new HashMap<String, String>();
fmap.put("name", fName);
fmap.put("signature", function.getSignature());
fmap.put("description", function.getDescription());
return Response.ok(fmap).build();
}
}
return Response.status(Status.NOT_FOUND).entity("LDPath function with name " + name + " does not exist").build();
} finally {
con.commit();
con.close();
}
} catch (RepositoryException e) {
return Response.serverError().entity(e).build();
}
}
/**
* Evaluate the LDPath program send as byte stream in the POST body of the request starting at the contexts (array)
* given as URL query arguments. Will return a JSON map with an entry for each context and its evaluation result.
* The value of each entry will have the following format:
* <ul>
* <li><code>{ "type": "uri", "value": "..." }</code> for resources</li>
* <li><code>{ "type": "literal", "value": "...", "language": "...", "datatype": "..."}</code> for literals (datatype and language optional)</li>
* </ul>
*
* @HTTP 200 in case the evaluation was successful for all contexts
* @HTTP 400 in case the LDPath program was invalid
* @HTTP 404 in case one of the contexts passed as argument does not exist
* @HTTP 500 in case there was an error accessing the repository or reading the POST body
*
* @param contextURI the URI of a single context to evaluate the program against
* @param contextURIarr an array of URIs to use as contexts to evaluate the program against
* @param request a POST request containing the LDPath program in the POST body
* @return a JSON map with an entry for each context pointing to its evaluation result (another map with field/value pairs)
*/
@POST
@Path("/debug")
@Produces("application/json")
public Response testProgram(@QueryParam("context") String[] contextURI, @QueryParam("context[]") String[] contextURIarr, @Context HttpServletRequest request) {
final String[] cs = contextURI != null ? contextURI : contextURIarr;
try {
// 1. read in the program from the post stream
String program = IOUtils.toString(request.getReader());
// 2. auto-register all namespaces that are defined in the triple store
Map<String,String> namespaces = new HashMap<String, String>();
RepositoryConnection con = sesameService.getConnection();
try {
con.begin();
for(Namespace ns : iterable(con.getNamespaces())) {
namespaces.put(ns.getPrefix(),ns.getName());
}
// 3. iterate over all context uris passed as argument and run the path query, storing the results
// in a hashmap where the context uris are the keys and a result map is the value
HashMap<String, Object> combined = new HashMap<String, Object>();
for(String context : cs) {
if (ResourceUtils.isSubject(con, context)) {
URI resource = con.getValueFactory().createURI(context);
Map<String,List<Map<String,String>>> result = new HashMap<String, List<Map<String, String>>>();
try {
for(Map.Entry<String,Collection<?>> row : ldPathService.programQuery(resource,program).entrySet()) {
List<Map<String,String>> rowList = new ArrayList<Map<String, String>>();
for(Object o : row.getValue()) {
if(o instanceof KiWiNode) {
rowList.add(JSONUtils.serializeNodeAsJson((Value) o));
} else {
// we convert always to a literal
rowList.add(JSONUtils.serializeNodeAsJson(new KiWiStringLiteral(o.toString())));
}
}
result.put(row.getKey(),rowList);
}
combined.put(context,result);
} catch (LDPathParseException e) {
log.warn("parse error while evaluating program {}: {}", program, e.getMessage());
return Response.status(Response.Status.BAD_REQUEST).entity("parse error while evaluating program: "+e.getMessage()).build();
}
} else {
return Response.status(Response.Status.NOT_FOUND).entity("resource "+context+" does not exist").build();
}
}
return Response.ok(combined).build();
} finally {
con.commit();
con.close();
}
} catch (RepositoryException ex) {
handleRepositoryException(ex,LDPathWebService.class);
return Response.serverError().entity("error accessing RDF repository: "+ex.getMessage()).build();
} catch(IOException ex) {
return Response.serverError().entity("error reading program from stream: "+ex.getMessage()).build();
}
}
}