From a4d865ff48e6265b3873bc0ec83d457bbcd5db37 Mon Sep 17 00:00:00 2001 From: enridaga Date: Fri, 24 Apr 2015 14:09:57 +0100 Subject: [PATCH] Modularization. See #5 --- basil/pom.xml | 34 +- .../ac/open/kmi/basil/AbstractResource.java | 70 ++ .../uk/ac/open/kmi/basil/ApiDocsResource.java | 54 ++ .../uk/ac/open/kmi/basil/ApiResource.java | 672 ++++++++++++++++++ .../ac/open/kmi/basil/BasilApplication.java | 46 ++ .../uk/ac/open/kmi/basil/DocsResource.java | 141 ++++ .../uk/ac/open/kmi/basil/ExplainResource.java | 66 ++ .../java/uk/ac/open/kmi/basil/Headers.java | 27 + .../uk/ac/open/kmi/basil/MoreMediaType.java | 52 ++ .../open/kmi/basil/SpecificationResource.java | 234 ++++++ .../uk/ac/open/kmi/basil/ViewResource.java | 151 ++++ .../java/uk/ac/open/kmi/basil/doc/Doc.java | 35 + .../ac/open/kmi/basil/server/Standalone.java | 109 +++ .../kmi/basil/sparql/ParameterException.java | 15 + .../open/kmi/basil/sparql/QueryParameter.java | 189 +++++ .../open/kmi/basil/sparql/Specification.java | 60 ++ .../basil/sparql/SpecificationFactory.java | 31 + .../open/kmi/basil/sparql/VariableParser.java | 130 ++++ .../kmi/basil/sparql/VariablesBinder.java | 170 +++++ .../kmi/basil/sparql/VariablesCollector.java | 148 ++++ .../uk/ac/open/kmi/basil/store/FileStore.java | 149 ++++ .../uk/ac/open/kmi/basil/store/Store.java | 29 + .../ac/open/kmi/basil/swagger/Bootstrap.java | 32 + .../open/kmi/basil/swagger/CustomFilter.java | 32 + .../kmi/basil/swagger/SwaggerJsonBuilder.java | 77 ++ .../uk/ac/open/kmi/basil/view/Engine.java | 70 ++ .../basil/view/EngineExecutionException.java | 16 + .../java/uk/ac/open/kmi/basil/view/Items.java | 107 +++ .../java/uk/ac/open/kmi/basil/view/View.java | 64 ++ .../java/uk/ac/open/kmi/basil/view/Views.java | 77 ++ .../kmi/basil/view/rhino/RhinoMediator.java | 111 +++ basil/src/main/webapp/WEB-INF/web.xml | 98 +++ basil/src/main/webapp/static/index.html | 1 + .../kmi/basil/sparql/QueryParameterTest.java | 120 ++++ .../ac/open/kmi/basil/sparql/TestUtils.java | 54 ++ .../kmi/basil/sparql/VariableParserTest.java | 204 ++++++ .../kmi/basil/sparql/VariablesBinderTest.java | 72 ++ .../basil/sparql/VariablesCollectorTest.java | 91 +++ .../open/kmi/basil/store/FileStoreTest.java | 112 +++ .../open/kmi/basil/view/JavascriptTest.java | 89 +++ .../ac/open/kmi/basil/view/MustacheTest.java | 74 ++ basil/src/test/resources/docs/select_1.txt | 7 + basil/src/test/resources/docs/select_3.txt | 5 + basil/src/test/resources/docs/select_7.txt | 4 + .../test/resources/javascript/script_1.tmpl | 4 + .../test/resources/javascript/select_1.tmpl | 10 + basil/src/test/resources/log4j2-jetty.xml | 15 + basil/src/test/resources/log4j2.xml | 15 + .../test/resources/mustache/mustache1.tmpl | 5 + .../src/test/resources/mustache/select_1.tmpl | 5 + .../src/test/resources/mustache/select_7.tmpl | 5 + basil/src/test/resources/sparql/ask_1.txt | 10 + .../src/test/resources/sparql/construct_1.txt | 12 + basil/src/test/resources/sparql/select_1.txt | 11 + basil/src/test/resources/sparql/select_2.txt | 15 + basil/src/test/resources/sparql/select_3.txt | 36 + basil/src/test/resources/sparql/select_4.txt | 24 + basil/src/test/resources/sparql/select_5.txt | 8 + basil/src/test/resources/sparql/select_6.txt | 33 + basil/src/test/resources/sparql/select_7.txt | 70 ++ parent/pom.xml | 182 +++++ pom.xml | 1 + 62 files changed, 4562 insertions(+), 28 deletions(-) create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/AbstractResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/ApiDocsResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/ApiResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/BasilApplication.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/DocsResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/ExplainResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/Headers.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/MoreMediaType.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/SpecificationResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/ViewResource.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/doc/Doc.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/server/Standalone.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/ParameterException.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/QueryParameter.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/Specification.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/SpecificationFactory.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariableParser.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesBinder.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesCollector.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/store/FileStore.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/store/Store.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/swagger/Bootstrap.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/swagger/CustomFilter.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/swagger/SwaggerJsonBuilder.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/Engine.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/EngineExecutionException.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/Items.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/View.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/Views.java create mode 100644 basil/src/main/java/uk/ac/open/kmi/basil/view/rhino/RhinoMediator.java create mode 100644 basil/src/main/webapp/WEB-INF/web.xml create mode 100644 basil/src/main/webapp/static/index.html create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/sparql/QueryParameterTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/sparql/TestUtils.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariableParserTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesBinderTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesCollectorTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/store/FileStoreTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/view/JavascriptTest.java create mode 100644 basil/src/test/java/uk/ac/open/kmi/basil/view/MustacheTest.java create mode 100644 basil/src/test/resources/docs/select_1.txt create mode 100644 basil/src/test/resources/docs/select_3.txt create mode 100644 basil/src/test/resources/docs/select_7.txt create mode 100644 basil/src/test/resources/javascript/script_1.tmpl create mode 100644 basil/src/test/resources/javascript/select_1.tmpl create mode 100644 basil/src/test/resources/log4j2-jetty.xml create mode 100644 basil/src/test/resources/log4j2.xml create mode 100644 basil/src/test/resources/mustache/mustache1.tmpl create mode 100644 basil/src/test/resources/mustache/select_1.tmpl create mode 100644 basil/src/test/resources/mustache/select_7.tmpl create mode 100644 basil/src/test/resources/sparql/ask_1.txt create mode 100644 basil/src/test/resources/sparql/construct_1.txt create mode 100644 basil/src/test/resources/sparql/select_1.txt create mode 100644 basil/src/test/resources/sparql/select_2.txt create mode 100644 basil/src/test/resources/sparql/select_3.txt create mode 100644 basil/src/test/resources/sparql/select_4.txt create mode 100644 basil/src/test/resources/sparql/select_5.txt create mode 100644 basil/src/test/resources/sparql/select_6.txt create mode 100644 basil/src/test/resources/sparql/select_7.txt create mode 100644 parent/pom.xml diff --git a/basil/pom.xml b/basil/pom.xml index ec37a83..e1e127a 100644 --- a/basil/pom.xml +++ b/basil/pom.xml @@ -3,6 +3,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + basil + parent + 0.2.0-SNAPSHOT + ../parent + basil basil 0.2.0-SNAPSHOT @@ -12,39 +18,25 @@ Tool for Building web Apis SImpLy on top of sparql endpoints - - UTF-8 - - 1.7.7 - - 2.1 - - 9.2.3.v20140905 - - org.slf4j slf4j-api - ${slf4j.version} org.apache.logging.log4j log4j-slf4j-impl - ${log4j.version} org.apache.logging.log4j log4j-api - ${log4j.version} org.apache.logging.log4j log4j-core - ${log4j.version} @@ -59,22 +51,18 @@ javax.servlet javax.servlet-api - 3.1.0 com.github.spullara.mustache.java compiler - 0.9.0 org.mozilla rhino - 1.7R5 commons-io commons-io - 2.4 log4j @@ -89,17 +77,14 @@ org.apache.commons commons-collections4 - 4.0 com.googlecode.json-simple json-simple - 1.1.1 org.apache.jena jena-core - 2.12.1 log4j @@ -114,7 +99,6 @@ org.apache.jena jena-arq - 2.12.1 log4j @@ -131,24 +115,20 @@ org.glassfish.jersey.containers jersey-container-servlet - 2.14 org.eclipse.jetty jetty-server - ${org.eclipse.jetty.version} org.eclipse.jetty jetty-webapp - ${org.eclipse.jetty.version} commons-cli commons-cli - 1.2 @@ -156,7 +136,6 @@ com.wordnik swagger-jersey2-jaxrs_2.10 compile - 1.3.12 javax.ws.rs @@ -167,7 +146,6 @@ com.thetransactioncompany cors-filter - 1.3.2 servlet-api diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/AbstractResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/AbstractResource.java new file mode 100644 index 0000000..f28b110 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/AbstractResource.java @@ -0,0 +1,70 @@ +package uk.ac.open.kmi.basil; + +import java.net.HttpURLConnection; +import java.net.URI; + +import javax.servlet.ServletContext; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.UriInfo; + +import uk.ac.open.kmi.basil.store.Store; + +public class AbstractResource { + + @Context + protected HttpHeaders requestHeaders; + + @Context + protected UriInfo requestUri; + + @Context + protected ServletContext context; + + protected final ResponseBuilder addHeaders(ResponseBuilder builder, + String id) { + URI api = requestUri.getBaseUriBuilder().path(id).path("api").build(); + URI spec = requestUri.getBaseUriBuilder().path(id).path("spec").build(); +// URI store = requestUri.getBaseUriBuilder().path(id).path("store") +// .build(); + URI views = requestUri.getBaseUriBuilder().path(id).path("view") + .build(); + URI swagger = requestUri.getBaseUriBuilder().path(id).path("api-docs") + .build(); + URI docs = requestUri.getBaseUriBuilder().path(id).path("docs") + .build(); + builder.header(Headers.Api, api); + builder.header(Headers.Spec, spec); + // builder.header(Headers.Store, store); XXX Not implemented Yet + builder.header(Headers.View, views); + builder.header(Headers.Docs, docs); + builder.header(Headers.Swagger, swagger); + return builder; + } + + protected final String getParameterOrHeader(String parameter, + boolean mandatory) { + String value = requestUri.getQueryParameters().getFirst(parameter); + if (value == null) { + if (requestHeaders.getHeaderString(Headers.getHeader(parameter)) == null) { + throw new WebApplicationException(Response + .status(HttpURLConnection.HTTP_BAD_REQUEST) + .header(Headers.Error, + Headers.getHeader(parameter) + + " (or query parameter '" + parameter + + "') missing.").build()); + } else { + value = requestHeaders.getHeaderString(Headers + .getHeader(parameter)); + } + } + return value; + } + + protected Store getDataStore() { + return (Store) context.getAttribute(BasilApplication.Registry.Store); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/ApiDocsResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/ApiDocsResource.java new file mode 100644 index 0000000..7b1bdcd --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/ApiDocsResource.java @@ -0,0 +1,54 @@ +package uk.ac.open.kmi.basil; + +import java.io.IOException; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.json.simple.JSONObject; + +import uk.ac.open.kmi.basil.doc.Doc; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.store.Store; +import uk.ac.open.kmi.basil.swagger.SwaggerJsonBuilder; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; + + +@Path("{id:([^/]+)}/api-docs") +@Api(value = "/basil", description = "BASIL operations") +public class ApiDocsResource extends AbstractResource { + + @GET + @Produces("application/json") + @ApiOperation(value = "Generates API Swagger Description") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") + }) + public Response get(@PathParam("id") String id) { + try { + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(404).build(); + } + + Specification specification = store.loadSpec(id); + Doc docs = store.loadDoc(id); + JSONObject o = SwaggerJsonBuilder.build(id, specification, docs, requestUri.getBaseUri().toString()); + ResponseBuilder builder = Response.ok(o.toJSONString()); + addHeaders(builder, id); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e); + } + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/ApiResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/ApiResource.java new file mode 100644 index 0000000..5f1a3a7 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/ApiResource.java @@ -0,0 +1,672 @@ +package uk.ac.open.kmi.basil; + +import com.hp.hpl.jena.graph.Node; +import com.hp.hpl.jena.graph.NodeFactory; +import com.hp.hpl.jena.graph.Triple; +import com.hp.hpl.jena.query.*; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.sparql.core.DatasetGraph; +import com.hp.hpl.jena.sparql.core.DatasetGraphFactory; +import com.hp.hpl.jena.sparql.core.DatasetGraphOne; +import com.hp.hpl.jena.sparql.resultset.CSVOutput; +import com.hp.hpl.jena.sparql.resultset.JSONOutput; +import com.hp.hpl.jena.sparql.resultset.XMLOutput; +import com.hp.hpl.jena.sparql.util.Context; +import com.wordnik.swagger.annotations.*; +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.riot.out.JsonLDWriter; +import org.apache.jena.riot.system.PrefixMap; +import org.apache.jena.riot.system.PrefixMapNull; +import org.apache.jena.riot.system.PrefixMapStd; +import org.apache.jena.riot.writer.*; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import uk.ac.open.kmi.basil.sparql.QueryParameter; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.VariablesBinder; +import uk.ac.open.kmi.basil.store.Store; +import uk.ac.open.kmi.basil.view.Items; +import uk.ac.open.kmi.basil.view.View; +import uk.ac.open.kmi.basil.view.Views; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Variant; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.*; + +@Path("{id:([^/]+)}/api{ext:(\\.[\\-a-zA-Z0-9]+)?}") +@Api(value = "/basil", description = "BASIL operations") +public class ApiResource extends AbstractResource { + + private Response performQuery(String id, + MultivaluedMap parameters, String extension) { + try { + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(404).build(); + } + + Specification specification = store.loadSpec(id); + VariablesBinder binder = new VariablesBinder(specification); + + List missing = new ArrayList(); + for (QueryParameter qp : specification.getParameters()) { + if (parameters.containsKey(qp.getName())) { + List values = parameters.get(qp.getName()); + binder.bind(qp.getName(), values.get(0)); + } else if (!qp.isOptional()) { + missing.add(qp.getName()); + } + } + + if (!missing.isEmpty()) { + StringBuilder ms = new StringBuilder(); + ms.append("Missing mandatory query parameters: "); + for (String p : missing) { + ms.append(p); + ms.append("\t"); + } + ms.append("\n"); + return Response.status(400).entity(ms.toString()).build(); + } + + Query q = binder.toQuery(); + QueryExecution qe = QueryExecutionFactory.sparqlService( + specification.getEndpoint(), q); + Object entity = null; + + MediaType type = null; + // If we have an extension + if (!extension.equals("")) { + // remove dot + extension = extension.substring(1); + type = getFromExtension(extension); + + // No extension, check if the extension is the name of a view + if (type == null) { + Views views = store.loadViews(id); + if (views.exists(extension)) { + View view = views.byName(extension); + StringWriter writer = new StringWriter(); + Items data = null; + if (q.isSelectType()) { + data = Items.create(qe.execSelect()); + } else if (q.isConstructType()) { + data = Items.create(qe.execConstructTriples()); + } else if (q.isAskType()) { + data = Items.create(qe.execAsk()); + } else if (q.isDescribeType()) { + data = Items.create(qe.execDescribeTriples()); + } else { + return Response + .serverError() + .entity("Unsupported query type: " + + q.getQueryType()).build(); + } + view.getEngine().exec(writer, view.getTemplate(), data); + // Yeah! + ResponseBuilder rb = Response.ok(writer.toString()); + addHeaders(rb, id); + rb.header("Content-Type", view.getMimeType() + + "; charset=utf-8"); + return rb.build(); + } + } + // Still found nothing? + if (type == null) { + return Response.status(404).entity("Not found\n").build(); + } + } + // No extension, look for best acceptable + if (type == null) { + type = getBestAcceptable(); + } + // No best acceptable + if (type == null) { + return buildNotAcceptable(); + } + + if (q.isSelectType()) { + entity = prepareEntity(type, qe.execSelect()); + } else if (q.isConstructType()) { + entity = prepareEntity(type, qe.execConstruct(), q + .getPrefixMapping().getNsPrefixMap()); + } else if (q.isAskType()) { + entity = prepareEntity(type, qe.execAsk()); + } else if (q.isDescribeType()) { + entity = prepareEntity(type, qe.execDescribe(), q + .getPrefixMapping().getNsPrefixMap()); + } else { + return Response.serverError() + .entity("Unsupported query type: " + q.getQueryType()) + .build(); + } + + // If entity is null then format is not acceptable + // ie we don't have an implementation of that object/type map + if (entity == null) { + return buildNotAcceptable(); + } + ResponseBuilder rb; + rb = Response.ok().entity(entity); + addHeaders(rb, id); + rb.header("Content-Type", type.withCharset("UTF-8").toString()); + return rb.build(); + } catch (Exception e) { + throw new WebApplicationException(e); + } + } + + private Response buildNotAcceptable() { + return Response.notAcceptable( + Variant.mediaTypes(MediaType.TEXT_PLAIN_TYPE).add().build()) + .build(); + } + + @SuppressWarnings("unchecked") + private Object prepareEntity(MediaType type, boolean b) { + + // text/plain + if (MediaType.TEXT_PLAIN_TYPE.equals(type)) { + if (b) { + return "True\n"; + } else { + return "False\n"; + } + } + + // xml + if (MediaType.TEXT_XML_TYPE.equals(type) + || MediaType.APPLICATION_XML_TYPE.equals(type)) { + StringBuilder p = new StringBuilder(); + p.append(""); + p.append("\n"); + p.append(""); + p.append("\n\t"); + p.append(""); + // boolean + p.append("\n\t\t"); + p.append(""); + p.append("boolean"); + p.append(""); + + p.append("\n\t"); + p.append(""); + + p.append("\n\t"); + p.append(""); + + p.append("\n\t\t"); + String v = "boolean"; + + p.append("<"); + p.append(v); + p.append(" "); + p.append("type=\""); + p.append("boolean"); + p.append("\""); + p.append(">"); + if (b) + p.append("true"); + else + p.append("false"); + p.append(""); + + p.append("\n\t"); + p.append(""); + + p.append("\n"); + p.append(""); + p.append("\n"); + return p.toString(); + } + + if (MediaType.APPLICATION_JSON_TYPE.equals(type)) { + JSONObject o = new JSONObject(); + o.put("bool", b); + return o.toJSONString() + "\n"; + } + + // sparql-results+xml + if (MoreMediaType.SPARQL_RESULTS_XML_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLOutput xOut = new XMLOutput(null); + xOut.format(baos, b); + return new String(baos.toByteArray()); + } + + // sparql-results+json + if (MoreMediaType.SPARQL_RESULTS_JSON_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + JSONOutput xOut = new JSONOutput(); + xOut.format(baos, b); + return new String(baos.toByteArray()); + } + return null; + } + + @SuppressWarnings("unchecked") + private Object prepareEntity(MediaType type, Model model, + Map prefixes) { + + // text/plain + if (MediaType.TEXT_PLAIN_TYPE.equals(type) + || MoreMediaType.NTRIPLES_TYPE.equals(type)) { + StringWriter sw = new StringWriter(); + NTriplesWriter.write(sw, model.getGraph().find(null, null, null)); + return sw.toString(); + } + + // xml + if (MediaType.TEXT_XML_TYPE.equals(type) + || MediaType.APPLICATION_XML_TYPE.equals(type)) { + Iterator tr = model.getGraph().find(null, null, null); + StringBuilder p = new StringBuilder(); + p.append(""); + p.append("\n"); + p.append(""); + p.append("\n\t"); + p.append(""); + // subject + p.append("\n\t\t"); + p.append(""); + p.append("subject"); + p.append(""); + // predicate + p.append("\n\t\t"); + p.append(""); + p.append("predicate"); + p.append(""); + // object + p.append("\n\t\t"); + p.append(""); + p.append("object"); + p.append(""); + p.append("\n\t"); + p.append(""); + p.append("\n\t"); + p.append(""); + while (tr.hasNext()) { + Triple t = tr.next(); + p.append("\n\t\t"); + p.append(""); + // subject + p.append("\n\t\t\t"); + String v = "subject"; + Node n = t.getSubject(); + p.append("<"); + p.append(v); +// p.append(" "); +// p.append("type=\""); +// if (n.isBlank()) { +// p.append("anon"); +// } else if (n.isLiteral()) { +// p.append("literal"); +// } else if (n.isURI()) { +// p.append("uri"); +// } +// p.append("\""); + p.append(">"); + p.append(n.toString()); + p.append(""); + + // predicate + p.append("\n\t\t\t"); + v = "predicate"; + n = t.getSubject(); + p.append("<"); + p.append(v); + // p.append(" "); + // p.append("type=\""); + // if (n.isBlank()) { + // p.append("anon"); + // } else if (n.isLiteral()) { + // p.append("literal"); + // } else if (n.isURI()) { + // p.append("uri"); + // } + // p.append("\""); + p.append(">"); + p.append(n.toString()); + p.append(""); + + // object + p.append("\n\t\t\t"); + v = "object"; + n = t.getSubject(); + p.append("<"); + p.append(v); +// p.append(" "); +// p.append("type=\""); +// if (n.isBlank()) { +// p.append("anon"); +// } else if (n.isLiteral()) { +// p.append("literal"); +// } else if (n.isURI()) { +// p.append("uri"); +// } +// p.append("\""); + p.append(">"); + p.append(n.toString()); + p.append(""); + + p.append("\n\t\t"); + p.append(""); + } + + p.append("\n\t"); + p.append(""); + p.append("\n"); + p.append(""); + p.append("\n"); + return p.toString(); + } + + // application/json + if (MediaType.APPLICATION_JSON_TYPE.equals(type)) { + Iterator tr = model.getGraph().find(null, null, null); + JSONObject o = new JSONObject(); + JSONArray vars = new JSONArray(); + vars.add("subject"); + vars.add("predicate"); + vars.add("object"); + o.put("vars", vars); + JSONArray items = new JSONArray(); + while (tr.hasNext()) { + Triple t = tr.next(); + JSONObject item = new JSONObject(); + item.put("subject", t.getSubject().toString()); + item.put("predicate", t.getPredicate().toString()); + item.put("object", t.getObject().toString()); + items.add(item); + } + o.put("items", items); + return o.toJSONString() + "\n"; + } + + // application/rdf+json + if (MoreMediaType.RDFJSON_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + RDFJSONWriter.output(baos, model.getGraph()); + try { + return new String(baos.toByteArray(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + // This will never happen + new WebApplicationException(500); + } + } + + // application/ld+json + if (MoreMediaType.JSONLD_TYPE.equals(type)) { + StringWriter w = new StringWriter(); + JsonLDWriter jw = new JsonLDWriter(RDFFormat.JSONLD_PRETTY); + PrefixMap p = new PrefixMapStd(); + p.putAll(prefixes); + jw.write(w, new DatasetGraphOne(model.getGraph()), p, null, + Context.emptyContext); + return w.toString(); + } + + // application/rdf+xml + if (MoreMediaType.RDFXML_TYPE.equals(type)) { + StringWriter w = new StringWriter(); + RDFXMLPlainWriter writer = new RDFXMLPlainWriter(); + PrefixMap p = new PrefixMapStd(); + p.putAll(prefixes); + writer.write(w, model.getGraph(), p, "", Context.emptyContext); + return w.toString(); + } + + // turtle + if (MoreMediaType.TEXT_TURTLE_TYPE.equals(type) + || MoreMediaType.APPLICATION_TURTLE_TYPE.equals(type)) { + StringWriter w = new StringWriter(); + TurtleWriter writer = new TurtleWriter(); + PrefixMap p = new PrefixMapStd(); + p.putAll(prefixes); + writer.write(w, model.getGraph(), p, "", Context.emptyContext); + return w.toString(); + } + + // text/x-nquads + if (MoreMediaType.TEXT_X_NQUADS_TYPE.equals(type)) { + StringWriter w = new StringWriter(); + NQuadsWriter writer = new NQuadsWriter(); + DatasetGraph dg = DatasetGraphFactory.createMem(); + dg.addGraph(NodeFactory.createURI(requestUri.getRequestUri() + .toString()), model.getGraph()); + writer.write(w, dg, PrefixMapNull.empty, null, Context.emptyContext); + return w.toString(); + } + + return null; + } + + @SuppressWarnings("unchecked") + private Object prepareEntity(MediaType type, ResultSet rs) { + // text/plain + if (MediaType.TEXT_PLAIN_TYPE.equals(type)) { + return ResultSetFormatter.asText(rs); + } + + // xml + if (MediaType.TEXT_XML_TYPE.equals(type) + || MediaType.APPLICATION_XML_TYPE.equals(type)) { + StringBuilder p = new StringBuilder(); + p.append(""); + p.append("\n"); + p.append(""); + p.append("\n\t"); + p.append(""); + for (String v : rs.getResultVars()) { + p.append("\n\t\t"); + p.append(""); + p.append(v); + p.append(""); + } + p.append("\n\t"); + p.append(""); + while (rs.hasNext()) { + QuerySolution r = rs.next(); + p.append("\n\t"); + p.append(""); + Iterator vn = r.varNames(); + while (vn.hasNext()) { + p.append("\n\t\t"); + String v = vn.next(); + RDFNode n = r.get(v); + p.append("<"); + p.append(v); +// p.append(" "); +// p.append("type=\""); +// if (n.isAnon()) { +// p.append("anon"); +// } else if (n.isLiteral()) { +// p.append("literal"); +// } else if (n.isURIResource()) { +// p.append("uri"); +// } else if (n.isResource()) { +// p.append("resource"); +// } +// p.append("\""); + p.append(">"); + p.append(n.toString()); + p.append(""); + } + p.append("\n\t"); + p.append(""); + } + p.append("\n"); + p.append(""); + p.append("\n"); + return p.toString(); + } + + // application/json + if (MediaType.APPLICATION_JSON_TYPE.equals(type)) { + JSONObject o = new JSONObject(); + JSONArray vars = new JSONArray(); + for (String v : rs.getResultVars()) { + vars.add(v); + } + o.put("vars", vars); + JSONArray items = new JSONArray(); + while (rs.hasNext()) { + QuerySolution t = rs.next(); + JSONObject item = new JSONObject(); + Iterator vi = t.varNames(); + while (vi.hasNext()) { +// JSONObject cell = new JSONObject(); + String vn = vi.next(); + RDFNode n = t.get(vn); +// if (n.isAnon()) { +// cell.put("type", "anon"); +// } else if (n.isLiteral()) { +// cell.put("type", "literal"); +// } else if (n.isURIResource()) { +// cell.put("type", "uri"); +// } +// cell.put("value", n.toString()); +// item.put(vn, cell); + item.put(vn, n.toString()); + } + items.add(item); + } + o.put("items", items); + return o.toJSONString() + "\n"; + } + + // sparql-results+xml + if (MoreMediaType.SPARQL_RESULTS_XML_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLOutput xOut = new XMLOutput(null); + xOut.format(baos, rs); + return new String(baos.toByteArray()); + } + + // sparql-results+json + if (MoreMediaType.SPARQL_RESULTS_JSON_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + JSONOutput xOut = new JSONOutput(); + xOut.format(baos, rs); + return new String(baos.toByteArray()); + } + + // text/csv + if (MoreMediaType.TEXT_CSV_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CSVOutput xOut = new CSVOutput(); + xOut.format(baos, rs); + return new String(baos.toByteArray()); + } + + // text/tsv + if (MoreMediaType.TEXT_TSV_TYPE.equals(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CSVOutput xOut = new CSVOutput(); + xOut.format(baos, rs); + return new String(baos.toByteArray()); + } + + return null; + } + + private List getAvailableVariants() { + return Arrays.asList(new MediaType[] { MediaType.TEXT_PLAIN_TYPE, + MoreMediaType.NTRIPLES_TYPE, MediaType.TEXT_XML_TYPE, + MediaType.APPLICATION_XML_TYPE, + MediaType.APPLICATION_JSON_TYPE, MoreMediaType.RDFJSON_TYPE, + MoreMediaType.RDFXML_TYPE, + MoreMediaType.APPLICATION_TURTLE_TYPE, + MoreMediaType.TEXT_TURTLE_TYPE, + MoreMediaType.TEXT_X_NQUADS_TYPE, + MoreMediaType.SPARQL_RESULTS_JSON_TYPE, + MoreMediaType.SPARQL_RESULTS_XML_TYPE, + MoreMediaType.TEXT_CSV_TYPE, MoreMediaType.TEXT_TSV_TYPE }); + } + + public MediaType getFromExtension(String ext) { + if (MoreMediaType.extensions.containsKey(ext)) { + return MoreMediaType.extensions.get(ext); + } else { + return null; + } + } + + /** + * + * @return null if none of the exisitng variants are acceptable + */ + private MediaType getBestAcceptable() { + // This list is sorted by the client preference + List acceptHeaders = requestHeaders + .getAcceptableMediaTypes(); + if (acceptHeaders == null || acceptHeaders.size() == 0) { + // Default type is text/plain + return MediaType.TEXT_PLAIN_TYPE; + } + + for (MediaType mt : acceptHeaders) { + String qValue = mt.getParameters().get("q"); + if (qValue != null && Double.valueOf(qValue).doubleValue() == 0.0) { + break; + } + + for (MediaType variant : getAvailableVariants()) { + if (variant.isCompatible(mt)) { + return variant; + } + } + + } + return null; + } + + @POST + @Consumes("application/x-www-form-urlencoded") + @ApiOperation(value = "Invoke API") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") } + ) + public Response post( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Extension of the output data format (e.g., .json, .xml)") + @PathParam("ext") String extension, + @ApiParam(value = "Input parameters") + MultivaluedMap form) { + return performQuery(id, form, extension); + } + + @GET + @ApiOperation(value = "Invoke API") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") } + ) + public Response get( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Extension of the output data format (e.g., .json, .xml)") + @PathParam("ext") String extension) { + return performQuery(id, requestUri.getQueryParameters(), extension); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/BasilApplication.java b/basil/src/main/java/uk/ac/open/kmi/basil/BasilApplication.java new file mode 100644 index 0000000..bce3213 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/BasilApplication.java @@ -0,0 +1,46 @@ +package uk.ac.open.kmi.basil; + +import com.wordnik.swagger.jersey.listing.ApiListingResourceJSON; +import com.wordnik.swagger.jersey.listing.JerseyApiDeclarationProvider; +import com.wordnik.swagger.jersey.listing.JerseyResourceListingProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.open.kmi.basil.store.FileStore; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.io.File; + +/** + * Created by Luca Panziera on 09/01/15. + */ +public class BasilApplication extends ResourceConfig implements ServletContextListener { + private Logger log = LoggerFactory.getLogger(BasilApplication.class); + + public BasilApplication(){ + packages("com.wordnik.swagger.jaxrs.json"). + packages("uk.ac.open.kmi.basil"). + register(ApiListingResourceJSON.class). + register(JerseyApiDeclarationProvider.class). + register(JerseyResourceListingProvider.class); + } + + public final static class Registry { + public final static String Store = "_Store"; + } + + public void contextDestroyed(ServletContextEvent arg0) { + + } + + public void contextInitialized(ServletContextEvent arg0) { + ServletContext ctx = arg0.getServletContext(); + String h = ctx.getInitParameter("file-store-home"); + File home = new File(h); + log.info("Preparing file store: {}", home); + home.mkdirs(); + ctx.setAttribute(Registry.Store, new FileStore(home)); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/DocsResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/DocsResource.java new file mode 100644 index 0000000..fcc257f --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/DocsResource.java @@ -0,0 +1,141 @@ +package uk.ac.open.kmi.basil; + +import java.io.IOException; + +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.doc.Doc; +import uk.ac.open.kmi.basil.doc.Doc.Field; +import uk.ac.open.kmi.basil.store.Store; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiParam; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; + +/** + * + * @author enridaga + * + */ +@Path("{id:([^/]+)}/docs") +@Api(value = "/basil", description = "BASIL operations") +public class DocsResource extends AbstractResource { + private Logger log = LoggerFactory.getLogger(DocsResource.class); + + @GET + @Produces("text/plain") + @ApiOperation(value = "API documentation") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") , + @ApiResponse(code = 204, message = "No content") + }) + public Response get(@PathParam("id") String id) { + log.trace("Calling GET. id={}",id); + try { + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(404).build(); + } + Doc doc = getDataStore().loadDoc(id); + ResponseBuilder builder; + if(doc.isEmpty()){ + builder = Response.noContent(); + }else{ + builder = Response.ok(doc.get(Field.DESCRIPTION)); + builder.header(Headers.Name, doc.get(Doc.Field.NAME)); + } + addHeaders(builder, id); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e); + } + } + + @DELETE + @ApiOperation(value = "Delete API documentation") + @ApiResponses(value = { + @ApiResponse(code = 500, message = "Internal error") , + @ApiResponse(code = 204, message = "Deleted. No content") + }) + public Response delete(@PathParam("id") String id) { + log.trace("Calling DELETE. id={}",id); + try { + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(404).build(); + } + boolean success = getDataStore().deleteDoc(id); + ResponseBuilder builder; + if(success){ + builder = Response.noContent(); + }else{ + builder = Response.serverError(); + } + addHeaders(builder, id); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e); + } + } + + @PUT + @Produces("text/plain") + @ApiOperation(value = "To create a new doc file (plain text) and/or set a name for the API", + notes = "The operation returns the resource URI of the doc file") + @ApiResponses(value = { @ApiResponse(code = 400, message = "Body cannot be empty"), + @ApiResponse(code = 201, message = "Doc file created"), + @ApiResponse(code = 409, message = "API does not exists (create the API first).") , + @ApiResponse(code = 500, message = "Internal error") }) + public Response put( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Name of the API", required = false) + @QueryParam("name") String name, + @ApiParam(value = "Description of the API", required = false) + String body) { + log.trace("Calling PUT. id={} name={}",id, name); + try { + log.trace("Body is: {}",body); + Store data = getDataStore(); + if(!data.existsSpec(id)){ + return Response.status(409).entity("API does not exists (create the API first).").build(); + } + Doc doc = data.loadDoc(id); + boolean created = true; + if (!doc.isEmpty()) { + created = false; + } + doc.set(Field.NAME, name); + doc.set(Field.DESCRIPTION, body); + data.saveDoc(id, doc); + ResponseBuilder builder; + if (created) { + builder = Response.created( + requestUri.getBaseUriBuilder().path(id).path(name).build()) + ; + } else { + builder = Response.ok(); + } + addHeaders(builder, id); + return builder.build(); + } catch (Exception e) { + return Response.serverError().entity(e).build(); + } + } + +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/ExplainResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/ExplainResource.java new file mode 100644 index 0000000..14947b6 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/ExplainResource.java @@ -0,0 +1,66 @@ +package uk.ac.open.kmi.basil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import uk.ac.open.kmi.basil.sparql.QueryParameter; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.VariablesBinder; +import uk.ac.open.kmi.basil.store.Store; + +import com.hp.hpl.jena.query.Query; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; + +@Path("{id:([^/]+)}/explain") +@Api(value = "/basil", description = "BASIL operations") +public class ExplainResource extends AbstractResource { + + @GET + @Produces("text/plain") + @ApiOperation(value = "Explain API invokation") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") + }) + public Response get(@PathParam("id") String id) { + try { + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(404).build(); + } + + Specification specification = store.loadSpec(id); + VariablesBinder binder = new VariablesBinder(specification); + + List missing = new ArrayList(); + for (QueryParameter qp : specification.getParameters()) { + if (requestUri.getQueryParameters().containsKey(qp.getName())) { + List values = requestUri.getQueryParameters().get(qp.getName()); + binder.bind(qp.getName(), values.get(0)); + } else if (!qp.isOptional()) { + missing.add(qp.getName()); + } + } + + Query q = binder.toQuery(); + ResponseBuilder builder = Response.ok(q.toString()); + addHeaders(builder, id); + builder.header(Headers.Endpoint, specification.getEndpoint()); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e); + } + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/Headers.java b/basil/src/main/java/uk/ac/open/kmi/basil/Headers.java new file mode 100644 index 0000000..4331b9e --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/Headers.java @@ -0,0 +1,27 @@ +package uk.ac.open.kmi.basil; + +public final class Headers { + private Headers() { + } + + public static final String PREFIX = "X-Basil-"; + public static final String Error = PREFIX + "Error"; + public static final String Endpoint = PREFIX + "Endpoint"; + public static final String Api = PREFIX + "Api"; + public static final String Spec = PREFIX + "Spec"; + public static final String View = PREFIX + "View"; + public static final String Store = PREFIX + "Store"; + public static final String Type = PREFIX + "Type"; + public static final String Docs = PREFIX + "Docs"; + public static final String Name = PREFIX + "Name"; + public static final String Swagger = PREFIX + "Swagger"; + + public static String getHeader(String parameter) { + return PREFIX + parameter.substring(0, 1).toUpperCase() + + parameter.substring(1).toLowerCase(); + } + + public static String asParameter(String Header) { + return Header.substring(PREFIX.length()).toLowerCase(); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/MoreMediaType.java b/basil/src/main/java/uk/ac/open/kmi/basil/MoreMediaType.java new file mode 100644 index 0000000..3b5b893 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/MoreMediaType.java @@ -0,0 +1,52 @@ +package uk.ac.open.kmi.basil; + +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +public class MoreMediaType { + public static final MediaType RDFXML_TYPE = new MediaType("application", + "rdf+xml"); + public static final MediaType RDFJSON_TYPE = new MediaType("application", + "rdf+json"); + public static final MediaType JSONLD_TYPE = new MediaType("application", + "ld+json"); + public static final MediaType TEXT_TURTLE_TYPE = new MediaType("text", + "turtle"); + public static final MediaType APPLICATION_TURTLE_TYPE = new MediaType( + "application", "turtle"); + public static final MediaType NTRIPLES_TYPE = new MediaType("application", + "n-triples"); + public static final MediaType TEXT_X_NQUADS_TYPE = new MediaType("text", + "x-nquads"); + public static final MediaType TEXT_TSV_TYPE = new MediaType("text", + "tsv"); + public static final MediaType TEXT_CSV_TYPE = new MediaType("text", + "csv"); + public static final MediaType SPARQL_RESULTS_XML_TYPE = new MediaType( + "application", "sparql-results+xml"); + public static final MediaType SPARQL_RESULTS_JSON_TYPE = new MediaType( + "application", "sparql-results+json"); + + public final static Map extensions = new HashMap() { + private static final long serialVersionUID = 1L; + public MediaType remove(Object key) { + throw new UnsupportedOperationException(); + }; + }; + static { + extensions.put("txt", MediaType.TEXT_PLAIN_TYPE); + extensions.put("xml", MediaType.APPLICATION_XML_TYPE); + extensions.put("json", MediaType.APPLICATION_JSON_TYPE); + extensions.put("sparql-json", MoreMediaType.SPARQL_RESULTS_JSON_TYPE); + extensions.put("sparql-xml", MoreMediaType.SPARQL_RESULTS_XML_TYPE); + extensions.put("ttl", MoreMediaType.TEXT_TURTLE_TYPE); + extensions.put("nt", MoreMediaType.NTRIPLES_TYPE); + extensions.put("nq", MoreMediaType.TEXT_X_NQUADS_TYPE); + extensions.put("jsonld", MoreMediaType.JSONLD_TYPE); + extensions.put("tsv", MoreMediaType.TEXT_TSV_TYPE); + extensions.put("csv", MoreMediaType.TEXT_CSV_TYPE); + } + +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/SpecificationResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/SpecificationResource.java new file mode 100644 index 0000000..e70a254 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/SpecificationResource.java @@ -0,0 +1,234 @@ +package uk.ac.open.kmi.basil; + +import com.wordnik.swagger.annotations.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.SpecificationFactory; +import uk.ac.open.kmi.basil.store.Store; + +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.UUID; + +@Path("/") +@Api(value = "/basil", description = "BASIL operations") +public class SpecificationResource extends AbstractResource { + + private static Logger log = LoggerFactory + .getLogger(SpecificationResource.class); + + /** + * Creates a new Stone + * + * @param body + * @return + */ + @PUT + @Produces("text/plain") + @ApiOperation(value = "Create a new API specification", + notes = "The operation returns the resource URI of the API specification", + response = URI.class) + @ApiResponses(value = { @ApiResponse(code = 400, message = "Body cannot be empty"), + @ApiResponse(code = 201, message = "Specification created"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response put(@ApiParam(value = "SPARQL endpoint of the data source") + @QueryParam("endpoint") String endpoint, + @ApiParam(value = "SPARQL query that defines the API specification", required = true) + String body) { + log.trace("Called PUT"); + String id = shortUUID(); + return doPUT(id, body); + } + + private Response doPUT(String id, String body) { + try { + if (body.equals("")) { + return Response.status(HttpURLConnection.HTTP_BAD_REQUEST) + .header(Headers.Error, "Body cannot be empty").build(); + } + + String endpoint = getParameterOrHeader( + Headers.asParameter(Headers.Endpoint), true); + + Specification specification = SpecificationFactory.create(endpoint, + body); + Store data = getDataStore(); + boolean created = true; + if (data.existsSpec(id)) { + created = false; + } + try { + data.saveSpec(id, specification); + } catch (IOException e) { + throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR); + } + + URI api = requestUri.getBaseUriBuilder().path(id).build(); + + ResponseBuilder response; + if (created) { + URI spec = requestUri.getBaseUriBuilder().path(id).path("spec") + .build(); + log.info("Created spec at: {}", spec); + response = Response.created(api).entity( + "Created: " + api.toString()); + } else { + URI spec = requestUri.getBaseUriBuilder().path(id).path("spec").build(); + log.info("Replaced spec at: {}", spec); + response = Response.ok(spec).entity( + "Replaced: " + spec.toString() + "\n"); + } + + addHeaders(response, id); + + return response.build(); + } catch (WebApplicationException e) { + log.error("", e); + throw e; + } + } + + /** + * Replace the spec of an API with a new version. + * + * @param id + * @param body + * @return + */ + @PUT + @Path("{id:([^/]+)}") + @Produces("text/plain") + @ApiOperation(value = "Update existing API specification", + response = URI.class) + @ApiResponses(value = { @ApiResponse(code = 400, message = "Body cannot be empty"), + @ApiResponse(code = 200, message = "Specification updated"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response replaceSpec( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam(value = "id") String id, + @ApiParam(value = "SPARQL query that substitutes the API specification", required = true) + String body) { + log.trace("Called PUT with id: {}", id); + return doPUT(id, body); + } + + /** + * Redirect to /spec + * @param id + * @return + */ + @GET + @Path("{id:([^/]+)}") + public Response redirectToSpec( + @PathParam(value = "id") String id) { + ResponseBuilder builder = Response.status(303); + addHeaders(builder, id); + return builder.location(requestUri.getBaseUriBuilder().path(id).path("spec").build()).build(); + } + /** + * Gets the spec of an API. + * + * @param id + * @return + */ + @GET + @Path("{id:([^/]+)}/spec") + @Produces("text/plain") + @ApiOperation(value = "Get the API specification") + @ApiResponses(value = { @ApiResponse(code = 404, message = "Specification not found"), + @ApiResponse(code = 200, message = "Specification found"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response getSpec( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam(value = "id") String id) { + log.trace("Called GET spec with id: {}", id); + try { + + Store store = getDataStore(); + if (!store.existsSpec(id)) { + return Response.status(Status.NOT_FOUND).build(); + } + + Specification spec = store.loadSpec(id); + ResponseBuilder response = Response.ok(); + response.header(Headers.Endpoint, spec.getEndpoint()); + addHeaders(response, id); + response.entity(spec.getQuery()); + + return response.build(); + } catch (Exception e) { + log.error("An error occurred", e); + throw new WebApplicationException(Response + .status(HttpURLConnection.HTTP_INTERNAL_ERROR) + .entity(e.getMessage()).build()); + } + } + + /** + * Delete an API + * + * @param id + * @return + */ + @DELETE + @Path("{id:([^/]+)}") + @Produces("text/plain") + @ApiOperation(value = "Delete API specification") + @ApiResponses(value = { @ApiResponse(code = 404, message = "Specification not found"), + @ApiResponse(code = 200, message = "Specification deleted"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response deleteSpec( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam(value = "id") String id) { + log.trace("Called DELETE spec with id: {}", id); + + // XXX Not Implemented + return Response.status(501).entity("Not implemented yet\n").build(); + + } + + /** + * List stones + * + * @return + */ + @GET + @Produces("text/plain") + @ApiOperation(value = "Get the list of available API specification",response = List.class) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") } + ) + public String list() { + log.trace("Called GET"); + Store data = getDataStore(); + StringBuilder sb = new StringBuilder(); + for (String stone : data.listSpecs()) { + sb.append(requestUri.getBaseUriBuilder().path(stone)); + sb.append("\n"); + } + return sb.toString(); + } + + /** + * Method to generate API Ids. + * + * XXX Not sure this is good, but it's an option - enridaga + * + * @return + * @see https://gist.github.com/LeeSanghoon/5811136 + */ + public static String shortUUID() { + UUID uuid = UUID.randomUUID(); + long l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong(); + return Long.toString(l, Character.MAX_RADIX); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/ViewResource.java b/basil/src/main/java/uk/ac/open/kmi/basil/ViewResource.java new file mode 100644 index 0000000..5da241e --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/ViewResource.java @@ -0,0 +1,151 @@ +package uk.ac.open.kmi.basil; + +import com.wordnik.swagger.annotations.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.open.kmi.basil.store.Store; +import uk.ac.open.kmi.basil.view.Engine; +import uk.ac.open.kmi.basil.view.View; +import uk.ac.open.kmi.basil.view.Views; + +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import java.io.IOException; +import java.util.List; + +@Path("{id:([^/]+)}/view") +@Api(value = "/basil", description = "BASIL operations") +public class ViewResource extends ApiResource { + private Logger log = LoggerFactory.getLogger(ViewResource.class); + + @PUT + @Path("{name:([^/]+)}") + @Produces("text/plain") + @ApiOperation(value = "Create a new API view", + notes = "The operation returns the resource URI of the API view") + @ApiResponses(value = { @ApiResponse(code = 400, message = "Body cannot be empty"), + @ApiResponse(code = 201, message = "View created"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response put( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Media type of the view output (e.g., text/html)", required = true) + @QueryParam("type") @DefaultValue("text/html") String type, + @ApiParam(value = "Name of the view", required = true) + @PathParam("name") String name, + @ApiParam(value = "Media type of the view", required = true) + @HeaderParam("Content-Type") String contentType, + @ApiParam(value = "Template of the view", required = true) + String body) { + try { + Engine engine; + // Content-type + if (requestHeaders.getMediaType() == null) { + engine = Engine.MUSTACHE; + } else { + String mediaType = requestHeaders.getMediaType().getType(); + engine = Engine.byContentType(mediaType); + if (engine == null) { + return Response.serverError() + .entity("Unsupported content type: " + mediaType) + .build(); + } + } + + Store data = getDataStore(); + Views views = data.loadViews(id); + boolean created = true; + if (views.exists(name)) { + created = false; + } + views.put(type, name, body, engine); + data.saveViews(id, views); + if (created) { + return Response.created( + requestUri.getBaseUriBuilder().path(id).path(name).build()) + .build(); + } else { + return Response.ok().build(); + } + } catch (Exception e) { + return Response.serverError().entity(e).build(); + } + } + + @GET + @Produces({"text/plain", "text/html"}) + @ApiOperation(value = "Get the list of available views of an API",response = List.class) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") } + ) + public Response listViews( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id) { + try { + Views views = getDataStore().loadViews(id); + if (views.numberOf() == 0) { + return Response.noContent().build(); + } + StringBuilder sb = new StringBuilder(); + for (String ext : views.getNames()) { + sb.append(ext).append("\n"); + } + return Response.ok(sb.toString()).build(); + } catch (Exception e) { + return Response.serverError().entity(e.getMessage()).build(); + } + } + + @GET + @Path("{name:([^/]+)}") + @ApiOperation(value = "See an API view") + @ApiResponses(value = { @ApiResponse(code = 404, message = "API view not found"), + @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response spec( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Name of the view", required = true) + @PathParam("name") String name) { + try { + Views views = getDataStore().loadViews(id); + View view = views.byName(name); + if (view == null) { + return Response.status(404).entity("Not found").build(); + } + ResponseBuilder builder = Response.ok(view.getTemplate()); + addHeaders(builder, id); + builder.header("Content-type", view.getEngine().getContentType()); + builder.header(Headers.Type, view.getMimeType()); + return builder.build(); + } catch (Exception e) { + return Response.serverError().entity(e).build(); + } + } + + @DELETE + @Path("{name:([^/]+)}") + @ApiOperation(value = "Delete API view") + @ApiResponses(value = { @ApiResponse(code = 404, message = "API view not found"), + @ApiResponse(code = 200, message = "API view deleted"), + @ApiResponse(code = 500, message = "Internal error") }) + public Response delete( + @ApiParam(value = "ID of the API specification", required = true) + @PathParam("id") String id, + @ApiParam(value = "Name of the view", required = true) + @PathParam("name") String name) { + Views views; + try { + views = getDataStore().loadViews(id); + views.remove(name); + getDataStore().saveViews(id, views); + log.debug("View deleted: {}:{} ", id, name); + return Response.noContent().build(); + } catch (IOException e) { + log.error("", e); + return Response.serverError().build(); + } + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/doc/Doc.java b/basil/src/main/java/uk/ac/open/kmi/basil/doc/Doc.java new file mode 100644 index 0000000..4a099c2 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/doc/Doc.java @@ -0,0 +1,35 @@ +package uk.ac.open.kmi.basil.doc; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author enridaga + * + */ +public class Doc implements Serializable { + /** + * + */ + private static final long serialVersionUID = 8091475762370792961L; + + public enum Field { + NAME, DESCRIPTION + } + + private Map doc = new HashMap(); + + public void set(Field field, String value) { + doc.put(field, value); + } + + public String get(Field f) { + return doc.get(f); + } + + public boolean isEmpty(){ + return get(Field.NAME) == null && get(Field.DESCRIPTION) == null; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/server/Standalone.java b/basil/src/main/java/uk/ac/open/kmi/basil/server/Standalone.java new file mode 100644 index 0000000..f63350f --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/server/Standalone.java @@ -0,0 +1,109 @@ +package uk.ac.open.kmi.basil.server; + +import java.io.File; +import java.io.PrintStream; + +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.webapp.WebAppContext; + +public class Standalone { + private static class Cli { + + private String[] args = null; + private Options options = new Options(); + + public Cli(String[] args) { + this.args = args; + options.addOption("h", "help", false, "Show this help."); + options.addOption("c", "config", true, + "Configuration file path (required)."); + options.addOption("p", "port", true, + "Set the port the server will listen to (defaults to 8080)."); + } + + /** + * Prints help. + */ + private void help() { + String syntax = "java [java-opts] -jar [jarfile] -c [config-file]"; + new HelpFormatter().printHelp(syntax, options); + System.exit(0); + } + + /** + * Parses command line arguments and acts upon them. + */ + public void parse() { + CommandLineParser parser = new BasicParser(); + CommandLine cmd = null; + try { + cmd = parser.parse(options, args); + if (cmd.hasOption('h')) + help(); + if (cmd.hasOption('p')) { + port = Integer.parseInt(cmd.getOptionValue('p')); + if (port < 0 && port > 65535) { + O.println("Invalid port number " + port + + ". Must be in the range [0,65535]."); + System.exit(100); + } + } + } catch (ParseException e) { + E.println("Failed to parse comand line properties"); + e.printStackTrace(); + help(); + } + } + + } + + private static PrintStream O = System.out; + private static PrintStream E = System.err; + private static int port = 8080; + + public static void main(String[] args) { + System.out.println("#1: welcome to the world's helthiest food"); + new Cli(args).parse(); + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + + connector.setIdleTimeout(1000 * 60 * 60); + connector.setSoLingerTime(-1); + connector.setPort(port); + server.setConnectors(new Connector[] { connector }); + System.out.println("#2: basil is starting on port " + port); + WebAppContext root = new WebAppContext(); + root.setContextPath("/"); + + String webxmlLocation = Standalone.class + .getResource("/WEB-INF/web.xml").toString(); + root.setDescriptor(webxmlLocation); + + String resLocation = Standalone.class + .getResource("/static").toString(); + root.setResourceBase(resLocation); + root.setParentLoaderPriority(true); + server.setHandler(root); + System.out.println("#3: done"); + + try { + server.start(); + System.out.println("#4: enjoy"); + server.join(); + System.out.println("#5: stopping server"); + } catch (Exception e) { + e.printStackTrace(); + System.exit(100); + } + System.out.println("#6: thank you"); + } + +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/ParameterException.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/ParameterException.java new file mode 100644 index 0000000..0d65aa7 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/ParameterException.java @@ -0,0 +1,15 @@ +package uk.ac.open.kmi.basil.sparql; + +public class ParameterException extends Exception { + + public ParameterException(){} + public ParameterException(String string) { + super(string); + } + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/QueryParameter.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/QueryParameter.java new file mode 100644 index 0000000..e0aae66 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/QueryParameter.java @@ -0,0 +1,189 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.io.Serializable; + +/** + * A query parameter of the produced Specification. Includes information about + * how the param value needs to be prepared to be bound in the SPARQL context. + * + * @author enridaga + * + */ +public class QueryParameter implements Serializable { + + public enum Type { + IRI, TypedLiteral, LangedLiteral, PlainLiteral, Number + } + + private static final long serialVersionUID = 6725251315049248905L; + private String name; + private String lang = null; + private boolean isOptional; + private String datatype = null; + private Type type = Type.PlainLiteral; // defaults to plain literal + + /** + * Name of the parameter. + * + * @return + */ + public String getName() { + return name; + } + + /** + * Sets the name of the parameter. + * + * @param name + */ + void setName(String name) { + this.name = name; + } + + /** + * If the value has to be treated as IRI in the SPARQL query. + * + * @return boolean true or false + */ + public boolean isIri() { + return type == Type.IRI; + } + + /** + * If the value has to be treated a number in the SPARQL query. + * + * @return boolean true or false + */ + public boolean isNumber() { + return type == Type.Number; + } + + /** + * The value has to be treated as IRI in the SPARQL query. + */ + void setIri() { + this.type = Type.IRI; + this.datatype = null; + this.lang = null; + } + + /** + * Whether the value has to treated as plain literal in the SPARQL query. + * + * @return boolean true or false. + */ + public boolean isPlainLiteral() { + return type == Type.PlainLiteral; + } + + /** + * Whether the value has to be treated as langed literal in the SPARQL + * query. The value of {@link #getLang()} has to be used as lang. + * + * @return boolean true or false. + */ + public boolean isLangedLiteral() { + return type == Type.LangedLiteral; + } + + /** + * Whether the value has to be treated as typed literal in the SPARQL query. + * The value of {@link #getDatatype()} has to be used as datatype. + * + * @return boolean true or false. + */ + public boolean isTypedLiteral() { + return type == Type.TypedLiteral; + } + + /** + * In case of langed literal, use this value as lang. + * + * @return the lang + */ + public String getLang() { + return lang; + } + + /** + * Sets this query parameter as langed literal, specifying what lang is to + * be used. + * + * @param lang + */ + void setLang(String lang) { + this.lang = lang; + this.type = Type.LangedLiteral; + this.datatype = null; + } + + /** + * Consider thie parameter as optional. + * + * @return boolean true or false. + */ + public boolean isOptional() { + return isOptional; + } + + /** + * Consider this parameter as optional + * + * @param isOptional + */ + void setOptional(boolean isOptional) { + this.isOptional = isOptional; + } + + /** + * Datatype to be used when preparing the value of the typed literal to be + * bound in the SPARQL query. + * + * @return the datatype iri as String. + */ + public String getDatatype() { + return datatype; + } + + /** + * Sets this query parameter as typed literal, specifying what datatype is + * to be used. + * + * @param datatype + */ + void setDatatype(String datatype) { + this.datatype = datatype; + this.type = Type.TypedLiteral; + this.lang = null; + } + + /** + * The value of this query parameter has to be used as simple plain literal. + * + */ + public void setPlainLiteral() { + this.type = Type.PlainLiteral; + this.datatype = null; + this.lang = null; + } + + @Override + public boolean equals(Object obj) { + boolean eq = (obj instanceof QueryParameter) + && (((QueryParameter) obj).type.equals(this.type)) + && (((QueryParameter) obj).getName().equals(this.getName())); + if (this.isLangedLiteral()) { + return this.getLang().equals((((QueryParameter) obj).getLang())); + } else if (this.isTypedLiteral()) { + return this.getDatatype().equals( + ((QueryParameter) obj).getDatatype()); + } + return eq; + } + + public void setNumber() { + this.type = Type.Number; + this.datatype = null; + this.lang = null; + } +} \ No newline at end of file diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/Specification.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/Specification.java new file mode 100644 index 0000000..f0a6d94 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/Specification.java @@ -0,0 +1,60 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class Specification implements Serializable { + private static final long serialVersionUID = 9010724117224824994L; + + private String endpoint; + private String query; + private Map variablesParameters = new HashMap(); + private Map parametersVariables = new HashMap(); + + public String getEndpoint() { + return endpoint; + } + + void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getQuery() { + return query; + } + + void setQuery(String query) { + this.query = query; + } + + public Collection getParameters() { + return Collections.unmodifiableCollection(variablesParameters.values()); + } + + void map(String variable, QueryParameter parameter) { + this.variablesParameters.put(variable, parameter); + this.parametersVariables.put(parameter.getName(), variable); + } + + public QueryParameter getParameter(String name) { + return variablesParameters.get(parametersVariables.get(name)); + } + + public QueryParameter getParameterOfVariable(String variable) { + return variablesParameters.get(variable); + } + public String getVariableOfParameter(String param) { + return parametersVariables.get(param); + } + + public boolean hasParameter(String name) { + return parametersVariables.containsKey(name); + } + + public boolean hasVariable(String name) { + return variablesParameters.containsKey(name); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/SpecificationFactory.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/SpecificationFactory.java new file mode 100644 index 0000000..83165ea --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/SpecificationFactory.java @@ -0,0 +1,31 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.util.Set; + +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.sparql.syntax.Element; +import com.hp.hpl.jena.sparql.syntax.ElementWalker; + +public class SpecificationFactory { + + public static Specification create(String endpoint, String sparql) { + VariablesCollector collector = new VariablesCollector(); + Query q = QueryFactory.create(sparql); + Element element = q.getQueryPattern(); + ElementWalker.walk(element, collector); + Set vars = collector.getVariables(); + VariableParser parser; + Specification spec = new Specification(); + spec.setEndpoint(endpoint); + spec.setQuery(sparql); + + for (String var : vars) { + parser = new VariableParser(var); + if (parser.isParameter()) { + spec.map(var, parser.getParameter()); + } + } + return spec; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariableParser.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariableParser.java new file mode 100644 index 0000000..dc5422c --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariableParser.java @@ -0,0 +1,130 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hp.hpl.jena.vocabulary.XSD; + +public class VariableParser { + private String variable; + private boolean isParameter = false; + private Map prefixes; + private boolean isError = false; + private Exception exception = null; + private QueryParameter p = null; + + public VariableParser(String variable) { + this.variable = variable; + this.prefixes = new HashMap(); + this.prefixes.put("xsd", XSD.getURI()); + try { + parse(); + } catch (ParameterException e) { + this.exception = e; + this.isError = true; + } + } + + public VariableParser(String variable, Map prefixes) { + this.variable = variable; + this.prefixes = prefixes; + try { + parse(); + } catch (ParameterException e) { + this.exception = e; + this.isError = true; + } + } + + private void parse() throws ParameterException { + Pattern pt = Pattern + .compile("[\\$\\?]([_]{1,2})([^_]+)_?([a-zA-Z0-9]+)?_?([a-zA-Z0-9]+)?.*$"); + Matcher m = pt.matcher(this.variable); + if (m.matches()) { + this.isParameter = true; + + this.p = new QueryParameter(); + p.setOptional(m.group(1).length() == 2); + p.setName(m.group(2)); + if (m.group(3) != null) { + if (m.group(3).toLowerCase().equals("iri")) { + p.setIri(); + } else if (m.group(3).toLowerCase().equals("literal")) { + p.setPlainLiteral(); + } else if (m.group(3).toLowerCase().equals("number")) { + p.setNumber(); + } else if (m.group(3).length() == 2 && m.group(4) == null) { + // specifies lang + p.setLang(m.group(3).toLowerCase()); + } else if (m.group(4) != null) { + String datatypePrefix = m.group(3); + String datatypeLocalName = m.group(4); + String namespace = prefixes.get(datatypePrefix); + if (namespace == null) { + exception = new ParameterException("Unknown prefix: " + + datatypePrefix); + isError = true; + } else { + p.setDatatype(namespace + datatypeLocalName); + } + } else { + // Let's check if group(3) is a well known XSD Datatype + Set xsdDatatypes = new HashSet(); + xsdDatatypes.add("string"); + xsdDatatypes.add("int"); + xsdDatatypes.add("integer"); + xsdDatatypes.add("boolean"); + xsdDatatypes.add("double"); + xsdDatatypes.add("long"); + xsdDatatypes.add("anyURI"); + xsdDatatypes.add("date"); + xsdDatatypes.add("dateTime"); + xsdDatatypes.add("gYear"); + xsdDatatypes.add("gMonth"); + // TODO add others + // ... + // + if (xsdDatatypes.contains(m.group(3))) { + String datatypeLocalName = m.group(3); + String datatype = XSD.getURI() + datatypeLocalName; + p.setDatatype(datatype); + } else { + isError = true; + this.exception = new ParameterException( + "Cannot recognize parameter properties."); + p.setPlainLiteral(); + } + } + } else { + p.setPlainLiteral(); + } + + } else { + // It's not a parameter + } + } + + public boolean isParameter() { + return isParameter; + } + + public QueryParameter getParameter() { + return p; + } + + public String getVariable() { + return variable; + } + + public boolean isError() { + return this.isError; + } + + public Exception getException() { + return this.exception; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesBinder.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesBinder.java new file mode 100644 index 0000000..b9dfc55 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesBinder.java @@ -0,0 +1,170 @@ +package uk.ac.open.kmi.basil.sparql; + +import com.hp.hpl.jena.datatypes.BaseDatatype; + +import com.hp.hpl.jena.query.ParameterizedSparqlString; +import com.hp.hpl.jena.query.Query; + +/** + * To bind parameter values to variables in the SPARQL query. + * + * @author enridaga + * + */ +public class VariablesBinder { + + private ParameterizedSparqlString pss; + private Specification spec; + + /** + * Constructor. + * + * @param spec + * @param bindings + * - Optional as sequence of strings: + * param,value,param2,value2... + * @see Specification + */ + public VariablesBinder(Specification spec, String... bindings) { + this.spec = spec; + this.pss = new ParameterizedSparqlString(spec.getQuery()); + for (int x = 0; x < bindings.length; x += 2) { + bind(bindings[x], bindings[x + 1]); + } + + } + + /** + * Binds a value to the parameter {@code name}. Delegetes to one of the + * specialized methods depending on the associated {@link QueryParameter}. + * Defaults to {@link #bindPlainLiteral(String, String)}. + * + * @param name + * @param value + * @return a reference to this object. + * @see #bindIri(String, String) + * @see #bindLangedLiteral(String, String, String) + * @see #bindPlainLiteral(String, String) + * @see #bindNumber(String, String) + * @see #bindTypedLiteral(String, String, String) + */ + public VariablesBinder bind(String name, String value) { + if (spec.hasParameter(name)) { + QueryParameter p = spec.getParameter(name); + if (p.isIri()) { + bindIri(name, value); + } else if (p.isLangedLiteral()) { + bindLangedLiteral(name, value, p.getLang()); + } else if (p.isTypedLiteral()) { + bindTypedLiteral(name, value, p.getDatatype()); + } else if (p.isNumber()) { + bindNumber(name, value); + } else { + // Default is PlainLiteral + bindPlainLiteral(name, value); + } + } + return this; + } + + /** + * Binds a value as typed literal. + * + * @param name + * @param value + * @param datatype + * @return a reference to this object. + */ + public VariablesBinder bindTypedLiteral(String name, String value, + String datatype) { + pss.setLiteral(spec.getVariableOfParameter(name), value, + new BaseDatatype(datatype)); + return this; + } + + /** + * Binds a value as typed literal. + * + * @param name + * @param value + * @param datatype + * @return a reference to this object. + */ + public VariablesBinder bindNumber(String name, String value) { + if (value.contains(".")) { + pss.setLiteral(spec.getVariableOfParameter(name), Double.valueOf(value)); + }else{ + pss.setLiteral(spec.getVariableOfParameter(name), Integer.valueOf(value)); + } + return this; + } + + /** + * Binds a value as simple literal. + * + * @param name + * @param value + * @return a reference to this object. + */ + public VariablesBinder bindPlainLiteral(String name, String value) { + pss.setLiteral(spec.getVariableOfParameter(name), value); + return this; + } + + /** + * Alias of {@link #bindPlainLiteral} + * + * @param name + * @param value + * @see #bindPlainLiteral + * @return a reference to this object. + */ + public VariablesBinder bindLiteral(String name, String value) { + return this.bindPlainLiteral(name, value); + } + + /** + * Binds a value as langed literal. + * + * @param name + * @param value + * @param lang + * @return a reference to this object. + */ + public VariablesBinder bindLangedLiteral(String name, String value, + String lang) { + pss.setLiteral(spec.getVariableOfParameter(name), value, lang); + return this; + } + + /** + * Binds a value as IRI. + * + * @param name + * @param value + * @return a reference to this object. + */ + public VariablesBinder bindIri(String name, String value) { + pss.setIri(spec.getVariableOfParameter(name), value); + return this; + } + + /** + * Returns the query with the replaced bindings. + * + * @return the query as String. + * + */ + public String toString() { + return toQuery().toString(); + } + + /** + * Returns the query with the replaced bindings. + * + * @return the query as Query. + */ + public Query toQuery() { + return pss.asQuery(); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesCollector.java b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesCollector.java new file mode 100644 index 0000000..209ff54 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/sparql/VariablesCollector.java @@ -0,0 +1,148 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import com.hp.hpl.jena.graph.Triple; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.sparql.core.TriplePath; +import com.hp.hpl.jena.sparql.core.Var; +import com.hp.hpl.jena.sparql.expr.Expr; +import com.hp.hpl.jena.sparql.syntax.Element; +import com.hp.hpl.jena.sparql.syntax.ElementAssign; +import com.hp.hpl.jena.sparql.syntax.ElementBind; +import com.hp.hpl.jena.sparql.syntax.ElementData; +import com.hp.hpl.jena.sparql.syntax.ElementDataset; +import com.hp.hpl.jena.sparql.syntax.ElementExists; +import com.hp.hpl.jena.sparql.syntax.ElementFilter; +import com.hp.hpl.jena.sparql.syntax.ElementGroup; +import com.hp.hpl.jena.sparql.syntax.ElementMinus; +import com.hp.hpl.jena.sparql.syntax.ElementNamedGraph; +import com.hp.hpl.jena.sparql.syntax.ElementNotExists; +import com.hp.hpl.jena.sparql.syntax.ElementOptional; +import com.hp.hpl.jena.sparql.syntax.ElementPathBlock; +import com.hp.hpl.jena.sparql.syntax.ElementService; +import com.hp.hpl.jena.sparql.syntax.ElementSubQuery; +import com.hp.hpl.jena.sparql.syntax.ElementTriplesBlock; +import com.hp.hpl.jena.sparql.syntax.ElementUnion; +import com.hp.hpl.jena.sparql.syntax.ElementVisitor; +import com.hp.hpl.jena.sparql.syntax.ElementWalker; + +public class VariablesCollector implements ElementVisitor { + + private Set variables = new HashSet(); + + public Set getVariables() { + return Collections.unmodifiableSet(variables); + } + + public void collect(String query){ + reset(); + Query q = QueryFactory.create(query); + Element element = q.getQueryPattern(); + ElementWalker.walk(element, this); + } + + public void reset() { + variables = new HashSet(); + } + + private void collectFromTriple(Triple triple) { + if (triple.getPredicate().isVariable()) { + variables.add(triple.getPredicate().toString()); + } + if (triple.getSubject().isVariable()) { + variables.add(triple.getSubject().toString()); + } + if (triple.getObject().isVariable()) { + variables.add(triple.getObject().toString()); + } + } + + private void collectFromExpr(Expr expr){ + for(Var var : expr.getVarsMentioned()){ + variables.add(var.toString()); + } + } + + public void visit(ElementTriplesBlock el) { + for (Triple triple : el.getPattern().getList()) { + collectFromTriple(triple); + } + } + + public void visit(ElementPathBlock el) { + for (TriplePath triple : el.getPattern().getList()) { + if(triple.isTriple()){ + collectFromTriple(triple.asTriple()); + }else{ + // is a path + if (triple.getSubject().isVariable()) { + variables.add(triple.getSubject().toString()); + } + if (triple.getObject().isVariable()) { + variables.add(triple.getObject().toString()); + } + } + } + } + + public void visit(ElementFilter el) { + collectFromExpr(el.getExpr()); + } + + public void visit(ElementAssign el) { + collectFromExpr(el.getExpr()); + } + + public void visit(ElementBind el) { + collectFromExpr(el.getExpr()); + } + + public void visit(ElementData el) { + // + } + + public void visit(ElementUnion el) { + // + } + + public void visit(ElementOptional el) { + // + } + + public void visit(ElementGroup el) { + // + } + + public void visit(ElementDataset el) { + // + } + + public void visit(ElementNamedGraph el) { + // + } + + public void visit(ElementExists el) { + // + } + + public void visit(ElementNotExists el) { + // + } + + public void visit(ElementMinus el) { + // + } + + public void visit(ElementService el) { + // + } + + public void visit(ElementSubQuery el) { + // + } + +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/store/FileStore.java b/basil/src/main/java/uk/ac/open/kmi/basil/store/FileStore.java new file mode 100644 index 0000000..e3c7b26 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/store/FileStore.java @@ -0,0 +1,149 @@ +package uk.ac.open.kmi.basil.store; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.doc.Doc; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.view.Views; + +public class FileStore implements Store { + private Logger log = LoggerFactory.getLogger(FileStore.class); + private File home; + + public FileStore(File home) { + this.home = home; + } + + protected File getFile(String id, String ext) throws IOException { + if (!home.isDirectory()) { + throw new IOException("Not a directory: " + home); + } + File newFile = new File(home, id + "." + ext); + return newFile; + } + + void write(String id, Serializable o, String ext) throws IOException { + log.trace("writing {}.{}", id, ext); + try { + File file = getFile(id, ext); + FileOutputStream fileOut = new FileOutputStream(file); + ObjectOutputStream out = new ObjectOutputStream(fileOut); + out.writeObject(o); + out.close(); + fileOut.close(); + } catch (IOException i) { + throw i; + } + } + + boolean delete(String id, String ext) throws IOException { + log.trace("deleting {}.{}", id, ext); + try { + File file = getFile(id, ext); + return file.delete(); + } catch (IOException i) { + throw i; + } + } + + + Object read(String id, String ext) throws IOException, + ClassNotFoundException { + log.trace("reading {}.{}", id, ext); + try { + Object o; + File file = getFile(id, ext); + FileInputStream fileIn = new FileInputStream(file); + ObjectInputStream in = new ObjectInputStream(fileIn); + o = in.readObject(); + in.close(); + fileIn.close(); + return o; + } catch (IOException i) { + throw i; + } catch (ClassNotFoundException c) { + throw c; + } + } + + public Specification loadSpec(String id) throws IOException { + try { + return (Specification) read(id, "spec"); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } catch (IOException e) { + throw e; + } + } + + public void saveSpec(String id, Specification spec) throws IOException { + write(id, spec, "spec"); + } + + public boolean existsSpec(String id) { + try { + return getFile(id, "spec").exists(); + } catch (IOException e) { + log.error("cannot test file: {}", id); + return false; + } + } + + public List listSpecs() { + List specs = new ArrayList(); + for (File f : org.apache.commons.io.FileUtils.listFiles(home, + new String[] { "spec" }, false)) { + specs.add(f.getName().substring(0, f.getName().lastIndexOf('.'))); + } + return specs; + } + + public Views loadViews(String id) throws IOException { + try { + try { + return (Views) read(id, "views"); + } catch (FileNotFoundException e) { + return new Views(); + } + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + } + + public Doc loadDoc(String id) throws IOException { + try { + try { + return (Doc) read(id, "doc"); + } catch (FileNotFoundException e) { + return new Doc(); + } + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + } + + public boolean deleteDoc(String id) throws IOException { + return delete(id, "doc"); + } + + public void saveViews(String id, Views views) throws IOException { + write(id, views, "views"); + } + + @Override + public void saveDoc(String id, Doc doc) throws IOException { + write(id, doc, "doc"); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/store/Store.java b/basil/src/main/java/uk/ac/open/kmi/basil/store/Store.java new file mode 100644 index 0000000..3cf2cf5 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/store/Store.java @@ -0,0 +1,29 @@ +package uk.ac.open.kmi.basil.store; + +import java.io.IOException; +import java.util.List; + +import uk.ac.open.kmi.basil.doc.Doc; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.view.Views; + +public interface Store { + + public void saveSpec(String id, Specification spec) throws IOException; + + public Specification loadSpec(String id) throws IOException; + + public boolean existsSpec(String id); + + public List listSpecs(); + + public Views loadViews(String id) throws IOException; + + public Doc loadDoc(String id) throws IOException; + + public void saveViews(String id, Views views) throws IOException; + + public void saveDoc(String id, Doc doc) throws IOException; + + public boolean deleteDoc(String id) throws IOException; +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/swagger/Bootstrap.java b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/Bootstrap.java new file mode 100644 index 0000000..d949b51 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/Bootstrap.java @@ -0,0 +1,32 @@ +package uk.ac.open.kmi.basil.swagger; + +import com.wordnik.swagger.config.ConfigFactory; +import com.wordnik.swagger.config.FilterFactory; +import com.wordnik.swagger.model.ApiInfo; + +import javax.servlet.http.HttpServlet; + +/** + * Created by Luca Panziera on 14/03/15. + */ +public class Bootstrap extends HttpServlet { + static { + // do any additional initialization here, such as set your base path programmatically as such: + // ConfigFactory.config().setBasePath("http://www.foo.com/"); + + // add a custom filter + FilterFactory.setFilter(new CustomFilter()); + + ApiInfo info = new ApiInfo( + "BASIL API", /* title */ + "Tool for building Web APIs on top of SPARQL endpoints.", + "", /* TOS URL */ + "", /* Contact */ + "Undefined", /* license */ + "" /* license URL */ + ); + + ConfigFactory.config().setApiInfo(info); + + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/swagger/CustomFilter.java b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/CustomFilter.java new file mode 100644 index 0000000..8175ab7 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/CustomFilter.java @@ -0,0 +1,32 @@ +package uk.ac.open.kmi.basil.swagger; + +import com.wordnik.swagger.core.filter.SwaggerSpecFilter; +import com.wordnik.swagger.model.ApiDescription; +import com.wordnik.swagger.model.Operation; +import com.wordnik.swagger.model.Parameter; + +/** + * Created by Luca Panziera on 14/03/15. + */ +public class CustomFilter implements SwaggerSpecFilter { + @Override + public boolean isOperationAllowed( + Operation operation, + ApiDescription api, + java.util.Map> params, + java.util.Map cookies, + java.util.Map> headers) { + return true; + } + + @Override + public boolean isParamAllowed( + Parameter parameter, + Operation operation, + ApiDescription api, + java.util.Map> params, + java.util.Map cookies, + java.util.Map> headers) { + return true; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/swagger/SwaggerJsonBuilder.java b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/SwaggerJsonBuilder.java new file mode 100644 index 0000000..28ec121 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/swagger/SwaggerJsonBuilder.java @@ -0,0 +1,77 @@ +package uk.ac.open.kmi.basil.swagger; + +import javax.ws.rs.core.MediaType; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import uk.ac.open.kmi.basil.MoreMediaType; +import uk.ac.open.kmi.basil.doc.Doc; +import uk.ac.open.kmi.basil.doc.Doc.Field; +import uk.ac.open.kmi.basil.sparql.QueryParameter; +import uk.ac.open.kmi.basil.sparql.Specification; + +public class SwaggerJsonBuilder { + public static enum Types { + List("List"), String("string"); + private String strval; + + Types(String strval) { + this.strval = strval; + } + + public String toString() { + return this.strval; + } + }; + + @SuppressWarnings("unchecked") + public static JSONObject build(String id, Specification spec, Doc doc, + String basilRoot) { + JSONObject root = new JSONObject(); + root.put("swaggerVersion", "1.2"); + root.put("basePath", basilRoot ); + root.put("resourcePath", id ); + JSONArray apis = new JSONArray(); + + // For each API + JSONObject api = new JSONObject(); + api.put("path", "/"+id+"/api"); + api.put("resourcePath", "/"+id+"/api"); + JSONArray operations = new JSONArray(); + JSONObject op = new JSONObject(); + op.put("method", "GET"); + op.put("nickname", "API"); + op.put("summary", doc.get(Field.DESCRIPTION)); + op.put("type", Types.List.toString()); + JSONArray produces = new JSONArray(); + for(MediaType kt : MoreMediaType.extensions.values()){ + produces.add(kt.toString()); + } + op.put("produces", produces); + JSONArray params = new JSONArray(); + JSONObject par; +// par= new JSONObject(); +// par.put("name", "id"); +// par.put("description", "Id of the BASIL API"); +// par.put("required", true); +// par.put("type", Types.String.toString()); +// par.put("paramType", "path"); +// params.add(par); + for (QueryParameter p : spec.getParameters()) { + par = new JSONObject(); + par.put("name", p.getName()); + par.put("description", ""); // TODO See issue #16 + par.put("required", !p.isOptional()); + par.put("type", Types.String.toString()); + par.put("paramType", "query"); + params.add(par); + } + op.put("parameters", params); + operations.add(op); + api.put("operations", operations); + apis.add(api); + root.put("apis", apis); + return root; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/Engine.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/Engine.java new file mode 100644 index 0000000..f03b74a --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/Engine.java @@ -0,0 +1,70 @@ +package uk.ac.open.kmi.basil.view; + +import java.io.StringReader; +import java.io.Writer; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Scriptable; + +import uk.ac.open.kmi.basil.view.rhino.RhinoMediator; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +public enum Engine { + MUSTACHE("template/mustache"), RHINO("application/javascript"); + private String contentType; + + Engine(String contentType) { + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public void exec(Writer writer, String template, Items data) + throws EngineExecutionException { + if (this.equals(MUSTACHE)) { + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf + .compile(new StringReader(template), template); + mustache.execute(writer, data); + } else if (this.equals(RHINO)) { + Context cx = Context.enter(); + try { + Scriptable scope = cx.initStandardObjects(); + cx.evaluateString(scope, "var rhinofunc = " + template, + "", 1, null); + Object fObj = scope.get("rhinofunc", scope); + if (fObj == Scriptable.NOT_FOUND || !(fObj instanceof Function)) { + throw new EngineExecutionException( + "Broken input: script should declare a single anonym function. Eg: function(){ /*your code here*/ }"); + } else { + Function f = (Function) fObj; + RhinoMediator sb = new RhinoMediator(scope, writer); + try { + sb.bindItems(data); + f.call(cx, scope, sb, new Object[] { sb }); + } catch (Exception e) { + throw new EngineExecutionException(e); + } + } + ; + } finally { + Context.exit(); + } + } + } + + public static Engine byContentType(String contentType) { + if (MUSTACHE.contentType.equals(contentType)) { + return MUSTACHE; + } else if (RHINO.contentType.equals(contentType)) { + return RHINO; + } + return null; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/EngineExecutionException.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/EngineExecutionException.java new file mode 100644 index 0000000..aec7cd8 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/EngineExecutionException.java @@ -0,0 +1,16 @@ +package uk.ac.open.kmi.basil.view; + +public class EngineExecutionException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + + public EngineExecutionException(Exception wrapped) { + super(wrapped); + } + + public EngineExecutionException(String message) { + super(message); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/Items.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/Items.java new file mode 100644 index 0000000..3aafe69 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/Items.java @@ -0,0 +1,107 @@ +package uk.ac.open.kmi.basil.view; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import com.hp.hpl.jena.graph.Triple; +import com.hp.hpl.jena.query.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; + +public class Items implements Callable>> { + private Iterator> items; + + public static Items create(List> items) { + Items o = new Items(); + o.items = items.iterator(); + return o; + } + + private Items() { + } + + public static Items create(final ResultSet rs) { + Items o = new Items(); + o.items = new Iterator>() { + public boolean hasNext() { + return rs.hasNext(); + } + + public Map next() { + QuerySolution qs = rs.next(); + Iterator vars = qs.varNames(); + Map item = new HashMap(); + while (vars.hasNext()) { + String var = vars.next(); + item.put(var, qs.get(var).toString()); + } + return item; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + return o; + } + + public static Items create(final Boolean rs) { + Items o = new Items(); + + o.items = new Iterator>() { + boolean hasNext = true; + + public boolean hasNext() { + return hasNext; + } + + public Map next() { + hasNext = false; + Map item = new HashMap(); + item.put("boolean", Boolean.toString(rs)); + return item; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + return o; + } + + public static Items create(final Iterator triples) { + Items o = new Items(); + o.items = new Iterator>() { + public boolean hasNext() { + return triples.hasNext(); + } + + public Map next() { + Triple qs = triples.next(); + Map item = new HashMap(); + item.put("s", qs.getSubject().toString()); + item.put("p", qs.getPredicate().toString()); + item.put("o", qs.getObject().toString()); + item.put("subject", qs.getSubject().toString()); + item.put("predicate", qs.getPredicate().toString()); + item.put("object", qs.getObject().toString()); + return item; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + return o; + } + + public Iterator> call() throws Exception { + return items; + } + + public Items items() { + return this; + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/View.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/View.java new file mode 100644 index 0000000..6f2de2f --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/View.java @@ -0,0 +1,64 @@ +package uk.ac.open.kmi.basil.view; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class View implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -5880624222164431668L; + + private String mimeType; + private String name; + private String template; + private Engine engine; + + public String getMimeType() { + return mimeType; + } + + void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getName() { + return name; + } + + void setExtension(String extension) { + this.name = extension; + } + + public String getTemplate() { + return template; + } + + void setTemplate(String template, Engine engine) { + this.template = template; + this.engine = engine; + } + + public Engine getEngine() { + return this.engine; + } + + public boolean equals(Object o) { + if (o instanceof View) { + View f = (View) o; + return f.engine.equals(this.engine) + && f.name.equals(this.name) + && f.mimeType.equals(this.mimeType) + && f.template.equals(this.template); + } + return false; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(engine).append(mimeType) + .append(name).append(template).build(); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/Views.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/Views.java new file mode 100644 index 0000000..0d2cccb --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/Views.java @@ -0,0 +1,77 @@ +package uk.ac.open.kmi.basil.view; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class Views implements Serializable { + /** + * + */ + private static final long serialVersionUID = -1537650181054324254L; + + private Map views = new HashMap(); + + public void put(String mimeType, String name, String template, Engine engine) { + View view = new View(); + view.setExtension(name); + view.setMimeType(mimeType); + view.setTemplate(template, engine); + views.put(name, view); + } + + public View byName(String name) { + return views.get(name); + } + + public boolean exists(String name) { + return views.containsKey(name); + } + + public Set byMimeType(String mimeType) { + Set viewset = new HashSet(); + for (Entry e : views.entrySet()) { + if (e.getValue().getMimeType().equals(mimeType)) { + viewset.add(e.getValue()); + } + } + return Collections.unmodifiableSet(viewset); + } + + public void remove(View view) { + views.remove(view.getName()); + } + + public void remove(String name) { + views.remove(name); + } + + public boolean supportsMimeType(String mimeType) { + for (Entry e : views.entrySet()) { + if (e.getValue().getMimeType().equals(mimeType)) { + return true; + } + } + return false; + } + + public Set getMimeTypes() { + Set mimeTypes = new HashSet(); + for (Entry e : views.entrySet()) { + mimeTypes.add(e.getValue().getMimeType()); + } + return Collections.unmodifiableSet(mimeTypes); + } + + public Set getNames() { + return Collections.unmodifiableSet(views.keySet()); + } + + public int numberOf() { + return views.size(); + } +} diff --git a/basil/src/main/java/uk/ac/open/kmi/basil/view/rhino/RhinoMediator.java b/basil/src/main/java/uk/ac/open/kmi/basil/view/rhino/RhinoMediator.java new file mode 100644 index 0000000..607b088 --- /dev/null +++ b/basil/src/main/java/uk/ac/open/kmi/basil/view/rhino/RhinoMediator.java @@ -0,0 +1,111 @@ +package uk.ac.open.kmi.basil.view.rhino; + +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.mozilla.javascript.BaseFunction; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.view.Items; + +public class RhinoMediator extends ScriptableObject { + private static Logger log = LoggerFactory.getLogger(RhinoMediator.class); + /** + * + */ + private static final long serialVersionUID = 1L; + // FIXME REMOVE + private Scriptable scope; + + public RhinoMediator(Scriptable scope, final Writer writer) { + this.scope = scope; + try { + // Initialize string builder + final Writer bridge = writer; + BaseFunction mf = new BaseFunction(scope, this) { + private static final long serialVersionUID = 1L; + + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + + for (Object o : args) { + try { + bridge.append(o.toString()); + } catch (IOException e) { + // How to handle this? + e.printStackTrace(); + } + } + return this; + } + }; + put("print", this, mf); + } catch (SecurityException e) { + log.error("", e); + } + } + + @Override + public String getClassName() { + return this.getClassName(); + } + + public void bindItems(Items items) throws Exception { + put("items", this, new ItemsWrapper(items.call())); + } + + class ItemsWrapper extends ScriptableObject { + private static final long serialVersionUID = 1L; + + public ItemsWrapper(final Iterator> iterator) { + put("hasNext", this, new BaseFunction() { + private static final long serialVersionUID = 1L; + + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + return iterator.hasNext(); + } + }); + put("next", this, new BaseFunction() { + private static final long serialVersionUID = 1L; + + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + final Map item = iterator.next(); + return new MapWrapper(item); + } + }); + } + + @Override + public String getClassName() { + return this.getClassName(); + } + + } + + class MapWrapper extends ScriptableObject { + private static final long serialVersionUID = 1L; + + public MapWrapper(final Map map) { + for (Entry e : map.entrySet()) { + put(e.getKey(), this, e.getValue()); + } + } + + @Override + public String getClassName() { + return getClassName(); + } + } +} diff --git a/basil/src/main/webapp/WEB-INF/web.xml b/basil/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..82aa6d7 --- /dev/null +++ b/basil/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,98 @@ + + + + + + + CORS + com.thetransactioncompany.cors.CORSFilter + + cors.allowOrigin + * + + + cors.supportedMethods + GET, POST, HEAD, PUT, DELETE, OPTIONS + + + cors.supportedHeaders + Accept, Origin, X-Requested-With, Content-Type, Last-Modified + + + cors.exposedHeaders + Set-Cookie + + + cors.supportsCredentials + true + + + + + CORS + /* + + + + BASIL + org.glassfish.jersey.servlet.ServletContainer + + javax.ws.rs.Application + uk.ac.open.kmi.basil.BasilApplication + + + jersey.config.server.wadl.disableWadl + true + + 1 + + + + file-store-home + ./basil-store + + + + BASIL + /basil/* + + + + Jersey2Config + com.wordnik.swagger.jersey.config.JerseyJaxrsConfig + + api.version + 0.1.0 + + + swagger.api.basepath + http://basil.kmi.open.ac.uk/basil + + 2 + + + + uk.ac.open.kmi.basil.BasilApplication + + + + Bootstrap + uk.ac.open.kmi.basil.swagger.Bootstrap + 2 + + + + default + /basil/docs/* + + + diff --git a/basil/src/main/webapp/static/index.html b/basil/src/main/webapp/static/index.html new file mode 100644 index 0000000..a5d0d22 --- /dev/null +++ b/basil/src/main/webapp/static/index.html @@ -0,0 +1 @@ +basil \ No newline at end of file diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/sparql/QueryParameterTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/QueryParameterTest.java new file mode 100644 index 0000000..b0cd875 --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/QueryParameterTest.java @@ -0,0 +1,120 @@ +package uk.ac.open.kmi.basil.sparql; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.QueryParameter; + +public class QueryParameterTest { + + private static Logger log = LoggerFactory + .getLogger(QueryParameterTest.class); + + private QueryParameter parameter; + + @Rule + public TestName method = new TestName(); + + @Before + public void before() { + log.info("{}", method.getMethodName()); + parameter = new QueryParameter(); + } + + @Test + public void getName() { + parameter.setName("myname"); + Assert.assertTrue(parameter.getName().equals("myname")); + } + + @Test + public void isIri() { + parameter.setIri(); + Assert.assertTrue(parameter.isIri()); // True + Assert.assertFalse(parameter.isLangedLiteral()); + Assert.assertFalse(parameter.isTypedLiteral()); + Assert.assertFalse(parameter.isPlainLiteral()); + Assert.assertNull(parameter.getDatatype()); + Assert.assertNull(parameter.getLang()); + } + + @Test + public void isPlainLiteral() { + parameter.setPlainLiteral(); + Assert.assertFalse(parameter.isIri()); + Assert.assertFalse(parameter.isLangedLiteral()); + Assert.assertFalse(parameter.isTypedLiteral()); + Assert.assertTrue(parameter.isPlainLiteral()); // True + Assert.assertNull(parameter.getDatatype()); + Assert.assertNull(parameter.getLang()); + } + + @Test + public void isLangedLiteral() { + parameter.setLang("en"); + Assert.assertFalse(parameter.isIri()); + Assert.assertTrue(parameter.isLangedLiteral()); // True + Assert.assertFalse(parameter.isTypedLiteral()); + Assert.assertFalse(parameter.isPlainLiteral()); + Assert.assertNull(parameter.getDatatype()); + Assert.assertEquals("en",parameter.getLang()); // en + } + + @Test + public void isTypedLiteral() { + parameter.setDatatype("http://www.example.org/datatype"); + Assert.assertFalse(parameter.isIri()); + Assert.assertFalse(parameter.isLangedLiteral()); + Assert.assertTrue(parameter.isTypedLiteral()); // True + Assert.assertFalse(parameter.isPlainLiteral()); + Assert.assertNotNull(parameter.getDatatype()); + Assert.assertEquals("http://www.example.org/datatype", parameter.getDatatype()); + Assert.assertNull(parameter.getLang()); + } + + + @Test + public void equals() { + QueryParameter parameter2 = new QueryParameter(); + parameter.setName("myname"); + parameter2.setName("myname"); + Assert.assertTrue(parameter.equals(parameter2)); + Assert.assertTrue(parameter2.equals(parameter2)); + Assert.assertTrue(parameter2.equals(parameter)); + + parameter.setIri(); + Assert.assertFalse(parameter.equals(parameter2)); + Assert.assertFalse(parameter2.equals(parameter)); + parameter2.setIri(); + Assert.assertTrue(parameter.equals(parameter2)); + Assert.assertTrue(parameter2.equals(parameter2)); + Assert.assertTrue(parameter.equals(parameter)); + + parameter.setLang("it"); + parameter2.setLang("en"); + Assert.assertFalse(parameter.equals(parameter2)); + Assert.assertFalse(parameter2.equals(parameter)); + parameter2.setLang("it"); + Assert.assertTrue(parameter.equals(parameter2)); + Assert.assertTrue(parameter2.equals(parameter2)); + Assert.assertTrue(parameter.equals(parameter)); + + parameter.setDatatype("http://www.example.org/datatype"); + parameter2.setDatatype("http://www.example.org/datatype2"); + Assert.assertFalse(parameter.equals(parameter2)); + Assert.assertFalse(parameter2.equals(parameter)); + parameter2.setLang("en"); + Assert.assertFalse(parameter.equals(parameter2)); + Assert.assertFalse(parameter2.equals(parameter)); + parameter2.setDatatype("http://www.example.org/datatype"); + Assert.assertTrue(parameter.equals(parameter2)); + Assert.assertTrue(parameter2.equals(parameter2)); + Assert.assertTrue(parameter.equals(parameter)); + + } +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/sparql/TestUtils.java b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/TestUtils.java new file mode 100644 index 0000000..9977a43 --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/TestUtils.java @@ -0,0 +1,54 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.io.IOException; + +import org.apache.commons.io.IOUtils; + +import uk.ac.open.kmi.basil.Headers; +import uk.ac.open.kmi.basil.sparql.QueryParameter; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.SpecificationFactory; +import uk.ac.open.kmi.basil.sparql.QueryParameter.Type; + +public class TestUtils { + + private static String loadQueryString(String qname) throws IOException { + return IOUtils.toString(TestUtils.class.getClassLoader() + .getResourceAsStream("./sparql/" + qname + ".txt"), "UTF-8"); + } + + private static String endpoint(String qname) { + int pos = qname.indexOf(Headers.Endpoint + ":"); + int len = (Headers.Endpoint + ":").length(); + int eol = qname.indexOf('\n', pos); + return qname.substring(pos + len, eol).trim(); + } + + public static Specification loadQuery(String fileName) throws IOException { + String sparql = loadQueryString(fileName); + String endpoint = endpoint(sparql); + System.out.println(endpoint); + return SpecificationFactory.create(endpoint, sparql); + } + + public static QueryParameter buildQueryParameter(String name, Type type, + String lang, String datatype) { + QueryParameter qp = new QueryParameter(); + qp.setName(name); + if (type == Type.IRI) { + qp.setIri(); + } else if (type == Type.TypedLiteral) { + qp.setDatatype(datatype); + } else if (type == Type.LangedLiteral) { + qp.setLang(lang); + } else if (type == Type.PlainLiteral) { + qp.setPlainLiteral(); + } + return qp; + } + + public static String loadTemplate(String type, String qname) throws IOException { + return IOUtils.toString(TestUtils.class.getClassLoader() + .getResourceAsStream("./" + type + "/" + qname + ".tmpl"), "UTF-8"); + } +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariableParserTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariableParserTest.java new file mode 100644 index 0000000..3610a1c --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariableParserTest.java @@ -0,0 +1,204 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.ParameterException; +import uk.ac.open.kmi.basil.sparql.VariableParser; + +import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; +import com.hp.hpl.jena.vocabulary.XSD; + +public class VariableParserTest { + + private static Logger log = LoggerFactory + .getLogger(VariableParserTest.class); + + @Rule + public TestName method = new TestName(); + + @Before + public void before() { + log.info("{}", method.getMethodName()); + } + + @Test + public void isApiParameter() { + + // A parameter + Assert.assertTrue(new VariableParser("$_type").isParameter()); + Assert.assertTrue(new VariableParser("?_type").isParameter()); + Assert.assertTrue(new VariableParser("?_name").isParameter()); + Assert.assertTrue(new VariableParser("?_type_").isParameter()); + // Not a parameter + Assert.assertFalse(new VariableParser("?type_").isParameter()); + Assert.assertFalse(new VariableParser("$type").isParameter()); + Assert.assertFalse(new VariableParser("$-type").isParameter()); + Assert.assertFalse(new VariableParser("type").isParameter()); + } + + @Test + public void getParameterNameOk() throws ParameterException { + + Assert.assertEquals("name", new VariableParser("$_name").getParameter() + .getName()); + Assert.assertEquals("name", new VariableParser("$_name_") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("?_name_literal") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("$_name_xsd_int") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("?_name_ex_dtype") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("$_name_en") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("?_name_it") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("$_name_gr") + .getParameter().getName()); + Assert.assertEquals("type", new VariableParser("?_type_iri") + .getParameter().getName()); + Assert.assertEquals("name", new VariableParser("?__name_it") + .getParameter().getName()); + } + + @Test + public void getParameterNameFailures() { + + List negatives = new ArrayList(); + negatives.add("$x"); + negatives.add("?name_literal"); + negatives.add("$name_xsd_int"); + negatives.add("?name_ex_dtype"); + negatives.add("$name_en"); + negatives.add("?name_it"); + negatives.add("$name_gr"); + negatives.add("?type_iri"); + for (String var : negatives) { + try { + new VariableParser(var).getParameter().getName(); + } catch (NullPointerException e) { + continue; + } + log.error("NotAParameterException was expected: {}", var); + Assert.assertTrue(false); + } + } + + @Test + public void isIri() throws ParameterException { + // True + Assert.assertTrue(new VariableParser("?_name_iri").getParameter() + .isIri()); + Assert.assertTrue(new VariableParser("?_name_iri_").getParameter() + .isIri()); + Assert.assertTrue(new VariableParser("?_name_iri_____").getParameter() + .isIri()); + // False + Assert.assertFalse(new VariableParser("?_name_literal").getParameter() + .isIri()); + Assert.assertFalse(new VariableParser("?_name").getParameter().isIri()); + Assert.assertFalse(new VariableParser("?_name_string").getParameter() + .isIri()); + Assert.assertFalse(new VariableParser("?_name_xsd_iri").getParameter() + .isIri()); + } + + @Test + public void isLiteral() throws ParameterException { + Assert.assertTrue(new VariableParser("?_name_literal").getParameter() + .isPlainLiteral()); + Assert.assertTrue(new VariableParser("$_type_literal").getParameter() + .isPlainLiteral()); + Assert.assertTrue(new VariableParser("?_literal_literal") + .getParameter().isPlainLiteral()); + Assert.assertTrue(new VariableParser("?_literal").getParameter() + .isPlainLiteral()); + // False + Assert.assertFalse(new VariableParser("$_literal_iri").getParameter() + .isPlainLiteral()); + + } + + @Test + public void isTypedLiteral() throws ParameterException { + + Assert.assertTrue(new VariableParser("?_literal_xsd_string") + .getParameter().isTypedLiteral()); + + Assert.assertTrue(new VariableParser("?_param_string").getParameter() + .isTypedLiteral()); + Assert.assertTrue(new VariableParser("?_param_boolean").getParameter() + .isTypedLiteral()); + Assert.assertTrue(new VariableParser("?_param_int").getParameter() + .isTypedLiteral()); + Assert.assertTrue(new VariableParser("?_param_integer").getParameter() + .isTypedLiteral()); + + Assert.assertFalse(new VariableParser("$_literal_xsd").getParameter() + .isTypedLiteral()); + Assert.assertFalse(new VariableParser("?_literal").getParameter() + .isTypedLiteral()); + + // Undefined prefixes + Assert.assertTrue(new VariableParser("?_name_rdf_HTML").isError()); + Assert.assertTrue(new VariableParser("$_type_ex_bob").isError()); + + } + + @Test + public void isLangedLiteral() throws ParameterException { + + // True: + Assert.assertTrue(new VariableParser("?_name_en").getParameter() + .isLangedLiteral()); + Assert.assertTrue(new VariableParser("?_name_it").getParameter() + .isLangedLiteral()); + Assert.assertTrue(new VariableParser("?_name_es").getParameter() + .isLangedLiteral()); + Assert.assertTrue(new VariableParser("?_name_ab").getParameter() + .isLangedLiteral()); + // False: + Assert.assertFalse(new VariableParser("?_name_abc").getParameter() + .isLangedLiteral()); + Assert.assertFalse(new VariableParser("?_name_a").getParameter() + .isLangedLiteral()); + Assert.assertFalse(new VariableParser("?_name_literal").getParameter() + .isLangedLiteral()); + Assert.assertFalse(new VariableParser("$_type_literal").getParameter() + .isLangedLiteral()); + Assert.assertFalse(new VariableParser("?_literal_literal") + .getParameter().isLangedLiteral()); + + Assert.assertFalse(new VariableParser("$_literal_iri").getParameter() + .isLangedLiteral()); + Assert.assertFalse(new VariableParser("?_literal").getParameter() + .isLangedLiteral()); + } + + @Test + public void getDatatype() throws ParameterException { + Assert.assertTrue(new VariableParser("$_paramname_xsd_string") + .getParameter().getDatatype() + .equals(XSDDatatype.XSDstring.getURI())); + Assert.assertTrue(new VariableParser("$_paramname_xsd_boolean") + .getParameter().getDatatype() + .equals(XSDDatatype.XSDboolean.getURI())); + + Map prefixes = new HashMap(); + prefixes.put("en", XSD.getURI()); + Assert.assertTrue(new VariableParser("$_paramname_en_string", prefixes) + .getParameter().getDatatype() + .equals(XSDDatatype.XSDstring.getURI())); + } +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesBinderTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesBinderTest.java new file mode 100644 index 0000000..ddbbf1a --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesBinderTest.java @@ -0,0 +1,72 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.VariablesBinder; +import uk.ac.open.kmi.basil.sparql.VariablesCollector; + +public class VariablesBinderTest { + + private static Logger log = LoggerFactory + .getLogger(VariablesBinderTest.class); + + private VariablesBinder binder; + + @Rule + public TestName method = new TestName(); + + @Before + public void before() { + log.info("{}", method.getMethodName()); + } + + @Test + public void select_1() throws IOException { + Specification spec = TestUtils.loadQuery(method.getMethodName()); + log.debug("before: \n{}\n", spec.getQuery()); + Assert.assertTrue(spec.hasVariable("?_geoid")); + Assert.assertTrue(spec.hasParameter("geoid")); + binder = new VariablesBinder(spec, "geoid", "2328926"); + log.debug("after: \n{}\n", binder.toString()); + VariablesCollector vars = new VariablesCollector(); + vars.collect(binder.toString()); + Assert.assertFalse(vars.getVariables().contains("?_geoid")); + } + + @Test + public void select_2() throws IOException{ + Specification spec = TestUtils.loadQuery(method.getMethodName()); + log.debug("before: \n{}\n", spec.getQuery()); + Assert.assertTrue(spec.hasParameter("term")); + Assert.assertTrue(spec.hasVariable("?_term")); + binder = new VariablesBinder(spec); + binder.bind( "term", "earthquake"); + log.debug("after: \n{}\n", binder.toString()); + VariablesCollector vars = new VariablesCollector(); + vars.collect(binder.toString()); + Assert.assertFalse(vars.getVariables().contains("?_term")); + } + + @Test + public void select_3() throws IOException{ + Specification spec = TestUtils.loadQuery(method.getMethodName()); + log.debug("before: \n{}\n", spec.getQuery()); + Assert.assertTrue(spec.hasParameter("code")); + Assert.assertTrue(spec.hasVariable("?_code_literal")); + binder = new VariablesBinder(spec); + binder.bindLiteral( "code", "A170"); + log.debug("after: \n{}\n", binder.toString()); + VariablesCollector vars = new VariablesCollector(); + vars.collect(binder.toString()); + Assert.assertFalse(vars.getVariables().contains("?_code_literal")); + } +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesCollectorTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesCollectorTest.java new file mode 100644 index 0000000..f2c8b6d --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/sparql/VariablesCollectorTest.java @@ -0,0 +1,91 @@ +package uk.ac.open.kmi.basil.sparql; + +import java.io.IOException; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.VariablesCollector; + +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.sparql.syntax.Element; +import com.hp.hpl.jena.sparql.syntax.ElementWalker; + +public class VariablesCollectorTest { + + private static Logger log = LoggerFactory + .getLogger(VariablesCollectorTest.class); + + private VariablesCollector collector = new VariablesCollector(); + + @Rule + public TestName testName = new TestName(); + + @Before + public void before(){ + log.info("{}", testName.getMethodName()); + } + + @After + public void after(){ + collector.reset(); + } + + private String loadQuery(String qname) throws IOException{ + return IOUtils.toString(getClass().getClassLoader().getResourceAsStream("./sparql/" + qname + ".txt"), "UTF-8"); + } + + private Set extractVars(String query){ + log.debug(" {}", query); + Query q = QueryFactory.create(query); + Element element = q.getQueryPattern(); + ElementWalker.walk(element, collector); + Set vars = collector.getVariables(); + log.debug(" > {}", vars); + return vars; + } + + @Test + public void select_1() throws IOException { + Set vars = extractVars(loadQuery(testName.getMethodName())); + Assert.assertTrue(vars.contains("?course")); + Assert.assertTrue(vars.contains("?location")); + Assert.assertTrue(vars.contains("?_geoid")); + } + + @Test + public void select_2() throws IOException { + Set vars = extractVars(loadQuery(testName.getMethodName())); + Assert.assertTrue(vars.contains("?description")); + Assert.assertTrue(vars.contains("?thing")); + Assert.assertTrue(vars.contains("?_term")); + } + + @Test + public void select_3() throws IOException { + Set vars = extractVars(loadQuery(testName.getMethodName())); + Assert.assertTrue(vars.contains("?x")); + Assert.assertTrue(vars.contains("?_code_literal")); + Assert.assertTrue(vars.contains("?title")); + Assert.assertTrue(vars.contains("?url")); + Assert.assertTrue(vars.contains("?apply")); + Assert.assertTrue(vars.contains("?numCredits")); + Assert.assertTrue(vars.contains("?description")); + } + + + @Test + public void select_4() throws IOException { + Set vars = extractVars(loadQuery(testName.getMethodName())); + Assert.assertTrue(vars.contains("?_x_iri")); + } +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/store/FileStoreTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/store/FileStoreTest.java new file mode 100644 index 0000000..ac226a5 --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/store/FileStoreTest.java @@ -0,0 +1,112 @@ +package uk.ac.open.kmi.basil.store; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.QueryParameter; +import uk.ac.open.kmi.basil.sparql.QueryParameter.Type; +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.SpecificationFactory; +import uk.ac.open.kmi.basil.sparql.TestUtils; + +public class FileStoreTest { + + private static Logger log = LoggerFactory.getLogger(FileStoreTest.class); + + private FileStore store; + private static File home; + + @BeforeClass + public static void beforeClass() throws URISyntaxException { + URL u = FileStoreTest.class.getClassLoader().getResource("."); + home = new File(new File(u.toURI()), "FileStoreTest"); + if (!home.exists()) { + home.mkdirs(); + } + } + + @Before + public void before() throws IOException { + log.info("{}", testName.getMethodName()); + FileUtils.deleteDirectory(home); + home.mkdirs(); + store = new FileStore(home); + + } + + @Rule + public TestName testName = new TestName(); + + @Test + public void serializeQueryParameter() throws IOException, + ClassNotFoundException { + QueryParameter param = TestUtils.buildQueryParameter("param1", + Type.LangedLiteral, "en", null); + store.write(testName.getMethodName(), param, "qp"); + QueryParameter param2 = (QueryParameter) store.read( + testName.getMethodName(), "qp"); + Assert.assertTrue(param2.getName().equals(param.getName())); + } + + @Test + public void serializeSpecification() throws IOException, + ClassNotFoundException { + Specification spec = SpecificationFactory.create( + "http://data.open.ac.uk/sparql", + "SELECT * WHERE {?X a ?_type_iri}"); + store.write(testName.getMethodName(), spec, "spec"); + Specification spec2 = (Specification) store.read( + testName.getMethodName(), "spec"); + Assert.assertTrue(spec.getEndpoint().equals(spec2.getEndpoint())); + Assert.assertTrue(spec.getQuery().equals(spec2.getQuery())); + Assert.assertTrue(spec.getParameters().iterator().next() + .equals(spec2.getParameters().iterator().next())); + } + + @Test + public void saveAndLoadSpec() throws IOException { + Specification spec = SpecificationFactory.create( + "http://data.open.ac.uk/sparql", + "SELECT * WHERE {?X a ?_type_iri}"); + store.saveSpec("myspecid", spec); + Specification spec2 = store.loadSpec("myspecid"); + Assert.assertTrue(spec.getEndpoint().equals(spec2.getEndpoint())); + Assert.assertTrue(spec.getQuery().equals(spec2.getQuery())); + Assert.assertTrue(spec.getParameters().iterator().next() + .equals(spec2.getParameters().iterator().next())); + } + + @Test + public void saveAndListSpec() throws IOException { + Specification spec = SpecificationFactory.create( + "http://data.open.ac.uk/sparql", + "SELECT * WHERE {?X a ?_type_iri}"); + store.saveSpec("myspecid", spec); + store.saveSpec("myspecid2", spec); + store.saveSpec("myspecid3", spec); + store.saveSpec("myspecid4", spec); + List stones = store.listSpecs(); + for (String id : stones) { + log.debug(" - {}", id); + } + Assert.assertTrue(stones.size() == 4); + stones.contains("myspecid"); + stones.contains("myspecid2"); + stones.contains("myspecid3"); + stones.contains("myspecid4"); + } + +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/view/JavascriptTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/view/JavascriptTest.java new file mode 100644 index 0000000..f51883a --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/view/JavascriptTest.java @@ -0,0 +1,89 @@ +package uk.ac.open.kmi.basil.view; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URISyntaxException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Scriptable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.ResultSet; + +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.TestUtils; +import uk.ac.open.kmi.basil.sparql.VariablesBinder; +import uk.ac.open.kmi.basil.view.rhino.RhinoMediator; + +public class JavascriptTest { + + private static Logger log = LoggerFactory.getLogger(JavascriptTest.class); + + @Rule + public TestName method = new TestName(); + + @Before + public void before() { + log.info("{}", method.getMethodName()); + } + + @Test + public void test() throws URISyntaxException, IOException { + String tmpl = TestUtils.loadTemplate("javascript", "script_1"); + Context cx = Context.enter(); + try { + Scriptable scope = cx.initStandardObjects(); + cx.evaluateString(scope, "var func = " + tmpl, "", 1, null); + Object fObj = scope.get("func", scope); + if (fObj == Scriptable.NOT_FOUND) { + log.error("x is not defined."); + } else { + log.info("Function: {}", Context.toString(fObj)); + } + if (!(fObj instanceof Function)) { + log.error("f is undefined or not a function."); + } else { + Function f = (Function) fObj; + StringWriter writer = new StringWriter(); + RhinoMediator sb = new RhinoMediator(scope, writer); + Object result = f.call(cx, scope, sb, new Object[] { sb }); + log.info("returns {}", result); + Assert.assertTrue(writer.toString() + .equals("this is a test")); + Assert.assertTrue(result instanceof RhinoMediator); + } + ; + } finally { + Context.exit(); + } + } + + @Test + public void select_1() throws IOException, EngineExecutionException { + Specification spec = TestUtils.loadQuery(method.getMethodName()); + String template = TestUtils.loadTemplate("javascript", method.getMethodName()); + VariablesBinder binder = new VariablesBinder(spec); + binder.bind("geoid", "2328926"); + + Query q = binder.toQuery(); + QueryExecution qe = QueryExecutionFactory.sparqlService( + spec.getEndpoint(), q); + final ResultSet rs = qe.execSelect(); + Writer writer = new StringWriter(); + Engine.RHINO.exec(writer, template, Items.create(rs)); +// writer.flush(); + log.info("\n{}", writer); + } + +} diff --git a/basil/src/test/java/uk/ac/open/kmi/basil/view/MustacheTest.java b/basil/src/test/java/uk/ac/open/kmi/basil/view/MustacheTest.java new file mode 100644 index 0000000..3f855e8 --- /dev/null +++ b/basil/src/test/java/uk/ac/open/kmi/basil/view/MustacheTest.java @@ -0,0 +1,74 @@ +package uk.ac.open.kmi.basil.view; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.open.kmi.basil.sparql.Specification; +import uk.ac.open.kmi.basil.sparql.TestUtils; +import uk.ac.open.kmi.basil.sparql.VariablesBinder; + +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.ResultSet; + +public class MustacheTest { + + private static Logger log = LoggerFactory.getLogger(MustacheTest.class); + + @Rule + public TestName method = new TestName(); + + @Before + public void before() { + log.info("{}", method.getMethodName()); + } + + @Test + public void test() throws URISyntaxException, IOException, + EngineExecutionException { + String tmpl = TestUtils.loadTemplate("mustache", "mustache1"); + List> items = new ArrayList>(); + Map row = new HashMap(); + row.put("name", "Enrico"); + items.add(row); + row = new HashMap(); + row.put("name", "Luca"); + items.add(row); + Writer writer = new StringWriter(); + Engine.MUSTACHE.exec(writer, tmpl, Items.create(items)); + writer.flush(); + log.debug("\n{}", writer); + } + + @Test + public void select_1() throws IOException, EngineExecutionException { + Specification spec = TestUtils.loadQuery(method.getMethodName()); + String template = TestUtils.loadTemplate("mustache", + method.getMethodName()); + VariablesBinder binder = new VariablesBinder(spec); + binder.bind("geoid", "2328926"); + + Query q = binder.toQuery(); + QueryExecution qe = QueryExecutionFactory.sparqlService( + spec.getEndpoint(), q); + final ResultSet rs = qe.execSelect(); + Writer writer = new StringWriter(); + Engine.MUSTACHE.exec(writer, template, Items.create(rs)); + writer.flush(); + log.debug("\n{}", writer); + } +} diff --git a/basil/src/test/resources/docs/select_1.txt b/basil/src/test/resources/docs/select_1.txt new file mode 100644 index 0000000..fae5d42 --- /dev/null +++ b/basil/src/test/resources/docs/select_1.txt @@ -0,0 +1,7 @@ +This API shows a list of courses available in the given geoid + +geoid must be a geonames location id + +results are data.open.ac.uk URIs + + diff --git a/basil/src/test/resources/docs/select_3.txt b/basil/src/test/resources/docs/select_3.txt new file mode 100644 index 0000000..6c10012 --- /dev/null +++ b/basil/src/test/resources/docs/select_3.txt @@ -0,0 +1,5 @@ +Information about a course under presentation. +Data comes from the XCRI feed. + +Try: DU311 ED841 E111 A219 L195 L161 L150 K101 + diff --git a/basil/src/test/resources/docs/select_7.txt b/basil/src/test/resources/docs/select_7.txt new file mode 100644 index 0000000..bf31280 --- /dev/null +++ b/basil/src/test/resources/docs/select_7.txt @@ -0,0 +1,4 @@ +Open Education Resources related to qualifications from the The Open University. + +Requires a qualification code, eg: q18, q51, q70, qd, q36, x86, w39, q61, q64 + diff --git a/basil/src/test/resources/javascript/script_1.tmpl b/basil/src/test/resources/javascript/script_1.tmpl new file mode 100644 index 0000000..5d60737 --- /dev/null +++ b/basil/src/test/resources/javascript/script_1.tmpl @@ -0,0 +1,4 @@ +function (items){ + this.print("this is a test"); + return this; +} \ No newline at end of file diff --git a/basil/src/test/resources/javascript/select_1.tmpl b/basil/src/test/resources/javascript/select_1.tmpl new file mode 100644 index 0000000..5804075 --- /dev/null +++ b/basil/src/test/resources/javascript/select_1.tmpl @@ -0,0 +1,10 @@ +function(){ + // returns a java.lang.Iterator on Entry + var items = this.items; + var c=0; + while(items.hasNext()) { + c++; + var item = items.next(); + this.print(c).print(' ').print(item.course).print("\n"); + } +}; \ No newline at end of file diff --git a/basil/src/test/resources/log4j2-jetty.xml b/basil/src/test/resources/log4j2-jetty.xml new file mode 100644 index 0000000..eea2369 --- /dev/null +++ b/basil/src/test/resources/log4j2-jetty.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/basil/src/test/resources/log4j2.xml b/basil/src/test/resources/log4j2.xml new file mode 100644 index 0000000..6546e49 --- /dev/null +++ b/basil/src/test/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/basil/src/test/resources/mustache/mustache1.tmpl b/basil/src/test/resources/mustache/mustache1.tmpl new file mode 100644 index 0000000..30ea65d --- /dev/null +++ b/basil/src/test/resources/mustache/mustache1.tmpl @@ -0,0 +1,5 @@ +
    +{{#items}} +
  • Name: {{name}}
  • +{{/items}} +
diff --git a/basil/src/test/resources/mustache/select_1.tmpl b/basil/src/test/resources/mustache/select_1.tmpl new file mode 100644 index 0000000..852f42e --- /dev/null +++ b/basil/src/test/resources/mustache/select_1.tmpl @@ -0,0 +1,5 @@ +
    +{{#items}} +
  1. Course: {{course}}
  2. +{{/items}} +
diff --git a/basil/src/test/resources/mustache/select_7.tmpl b/basil/src/test/resources/mustache/select_7.tmpl new file mode 100644 index 0000000..b2fef7a --- /dev/null +++ b/basil/src/test/resources/mustache/select_7.tmpl @@ -0,0 +1,5 @@ +
    +{{#items}} +
  1. {{title}} ({{type}})
    {{link}}
  2. +{{/items}} +
diff --git a/basil/src/test/resources/sparql/ask_1.txt b/basil/src/test/resources/sparql/ask_1.txt new file mode 100644 index 0000000..dfa308c --- /dev/null +++ b/basil/src/test/resources/sparql/ask_1.txt @@ -0,0 +1,10 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +PREFIX xsd: +PREFIX mlo: +PREFIX aiiso: + +# Eg value for ?_geoid 2328926 +ASK { + BIND(iri(CONCAT('http://sws.geonames.org/',?_geoid,'/')) as ?location) . + ?course mlo:location ?location . ?course a aiiso:Module +} diff --git a/basil/src/test/resources/sparql/construct_1.txt b/basil/src/test/resources/sparql/construct_1.txt new file mode 100644 index 0000000..3758cfd --- /dev/null +++ b/basil/src/test/resources/sparql/construct_1.txt @@ -0,0 +1,12 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +PREFIX xsd: +PREFIX mlo: +PREFIX aiiso: + +# Eg value for ?_geoid 2328926 +CONSTRUCT { +?course mlo:location ?location . ?course a aiiso:Module +}WHERE{ + BIND(iri(CONCAT('http://sws.geonames.org/',?_geoid,'/')) as ?location) . + ?course mlo:location ?location . ?course a aiiso:Module +} diff --git a/basil/src/test/resources/sparql/select_1.txt b/basil/src/test/resources/sparql/select_1.txt new file mode 100644 index 0000000..4500148 --- /dev/null +++ b/basil/src/test/resources/sparql/select_1.txt @@ -0,0 +1,11 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +PREFIX xsd: +PREFIX mlo: +PREFIX aiiso: + +# Eg value for ?_geoid 2328926 +SELECT ?course FROM +WHERE { + BIND(iri(CONCAT('http://sws.geonames.org/',?_geoid,'/')) as ?location) . + ?course mlo:location ?location . ?course a aiiso:Module +} diff --git a/basil/src/test/resources/sparql/select_2.txt b/basil/src/test/resources/sparql/select_2.txt new file mode 100644 index 0000000..33c8397 --- /dev/null +++ b/basil/src/test/resources/sparql/select_2.txt @@ -0,0 +1,15 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +# +# "earthquake" + +PREFIX mlo: + +SELECT ?thing ?description + +FROM +FROM +where { +?thing ?description . +FILTER EXISTS { { ?thing a +} UNION { ?thing a } } +. FILTER regex(str(?description), ?_term, "i" ) } \ No newline at end of file diff --git a/basil/src/test/resources/sparql/select_3.txt b/basil/src/test/resources/sparql/select_3.txt new file mode 100644 index 0000000..95d252c --- /dev/null +++ b/basil/src/test/resources/sparql/select_3.txt @@ -0,0 +1,36 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +PREFIX dc: +PREFIX dc11: +PREFIX oucourse: +PREFIX oco: +PREFIX courseware: +PREFIX aiiso: +PREFIX mlo: +PREFIX gr: +PREFIX xcri: +PREFIX rdfs: +PREFIX cm: + +# code might be DU311 ED841 E111 A219 L195 L161 L150 K101 ... + +SELECT DISTINCT +?x +?code +?title +?url +?OUCourseLevel +?numCredits +?description +?apply +?globalRedirect +FROM +WHERE +{ +?x a xcri:course . +?x xcri:internalID ?_code_literal . +?x dc11:title ?title . +?x mlo:url ?url . +?x mlo:specifies/xcri:applyUntil ?apply . +?x mlo:credit [ cm:level ?OUCourseLevel ; cm:scheme ; cm:value ?numCredits ] . +?x dc11:description ?description . +} diff --git a/basil/src/test/resources/sparql/select_4.txt b/basil/src/test/resources/sparql/select_4.txt new file mode 100644 index 0000000..c9b84b5 --- /dev/null +++ b/basil/src/test/resources/sparql/select_4.txt @@ -0,0 +1,24 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +PREFIX dc: +PREFIX dc11: +PREFIX oucourse: +PREFIX oco: +PREFIX courseware: +PREFIX aiiso: +PREFIX mlo: +PREFIX gr: +PREFIX xcri: +PREFIX rdfs: +PREFIX cm: +PREFIX owl: + +SELECT DISTINCT ?type +WHERE { + SERVICE { + ?_x_iri a ?type + } . + SERVICE { + ?_x_iri a ?type + } . + +} \ No newline at end of file diff --git a/basil/src/test/resources/sparql/select_5.txt b/basil/src/test/resources/sparql/select_5.txt new file mode 100644 index 0000000..15236f8 --- /dev/null +++ b/basil/src/test/resources/sparql/select_5.txt @@ -0,0 +1,8 @@ +# X-Basil-Endpoint: http://dbpedia.org/sparql +select ?subject ?label ?nationality where { + ?subject a ; + ?label ; + ?_year_number ; + ?nationality . + filter(lang(?label)="en") + } diff --git a/basil/src/test/resources/sparql/select_6.txt b/basil/src/test/resources/sparql/select_6.txt new file mode 100644 index 0000000..b18453a --- /dev/null +++ b/basil/src/test/resources/sparql/select_6.txt @@ -0,0 +1,33 @@ +# X-Basil-Endpoint: http://data.open.ac.uk/sparql +prefix rdf: +prefix rdfs: +prefix owl: +prefix xsd: +prefix skos: +prefix foaf: +prefix scovo: +prefix void: +prefix qb: +prefix dcterms: +prefix odrl: +prefix li: +prefix cc: +prefix odrl: +prefix bibo: + +select ?publication ?title ?date ?publisher ?presentedAt +from +from +where { + ?publication dcterms:creator [ + foaf:account [ + a ; + foaf:accountName ?_oucu_xsd_string +] ] ; +dcterms:title ?title ; +dcterms:date ?date . +optional { ?publication dcterms:publisher [ foaf:name ?publisher ] } . +optional { ?publication bibo:presentedAt ?presentedAt } + +} + diff --git a/basil/src/test/resources/sparql/select_7.txt b/basil/src/test/resources/sparql/select_7.txt new file mode 100644 index 0000000..b0bdfae --- /dev/null +++ b/basil/src/test/resources/sparql/select_7.txt @@ -0,0 +1,70 @@ + prefix rdf: + prefix podcast: + prefix audioboo: + prefix yt: + prefix rdfs: + prefix rkb: + prefix saou: + prefix dbp: + prefix media: + prefix olearn: + prefix mlo: + prefix bazaar: + prefix schema: + + SELECT + distinct + (?related as ?identifier) + ?type + (str(?label) as ?title) + (str(?location) as ?link) + + FROM + FROM + FROM + FROM + FROM + FROM + FROM + WHERE + { + # TRY: q18, q51, q70, qd, q36, x86, w39, q61, q64 + BIND(iri(concat("http://data.open.ac.uk/qualification/",?_qid)) AS ?qualification) . + { + # related video podcasts + ?related podcast:relatesToQualification ?qualification . + ?related a podcast:VideoPodcast . + ?related rdfs:label ?label . + optional { ?related bazaar:download ?location } + BIND( "VideoPodcast" as ?type ) . + } union { + # related audio podcasts + ?related podcast:relatesToQualification ?qualification . + ?related a podcast:AudioPodcast . + ?related rdfs:label ?label . + optional { ?related bazaar:download ?location } + BIND( "AudioPodcast" as ?type ) . + } union { + # related audioboo posts + ?related audioboo:relatesToQualification ?qualification . + ?related a audioboo:AudioBooPost . + ?related rdfs:label ?label . + optional { ?related rdfs:seeAlso ?location } + BIND( "AudiobooPost" as ?type ) . + } union { + # related openlearn units + ?related a olearn:OpenLearnUnit . + ?related olearn:relatesToQualification ?qualification . + BIND( "OpenLearnUnit" as ?type ) . + ?related ?location . + ?related rdfs:label ?label . + } union { + # related youtube videos + ?related a schema:VideoObject . + ?related yt:relatesToQualification ?qualification . + BIND( "YoutubeVideo" as ?type ) . + ?related media:download ?location . + ?related rdfs:label ?label . + } + } + diff --git a/parent/pom.xml b/parent/pom.xml new file mode 100644 index 0000000..4e9b282 --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,182 @@ + + + 4.0.0 + + basil + parent + 0.2.0-SNAPSHOT + pom + + BASIL :: Parent + + Tool for Building web Apis SImpLy on top of sparql endpoints + + + UTF-8 + + 1.7.7 + + 2.1 + + 9.2.3.v20140905 + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + + junit + junit + 4.12 + test + + + + + javax.servlet + javax.servlet-api + 3.1.0 + + + com.github.spullara.mustache.java + compiler + 0.9.0 + + + org.mozilla + rhino + 1.7R5 + + + commons-io + commons-io + 2.4 + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + + org.apache.commons + commons-collections4 + 4.0 + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + org.apache.jena + jena-core + 2.12.1 + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + + org.apache.jena + jena-arq + 2.12.1 + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + + + + org.glassfish.jersey.containers + jersey-container-servlet + 2.14 + + + org.eclipse.jetty + jetty-server + ${org.eclipse.jetty.version} + + + org.eclipse.jetty + jetty-webapp + ${org.eclipse.jetty.version} + + + + + commons-cli + commons-cli + 1.2 + + + + + com.wordnik + swagger-jersey2-jaxrs_2.10 + compile + 1.3.12 + + + javax.ws.rs + jsr311-api + + + + + com.thetransactioncompany + cors-filter + 1.3.2 + + + servlet-api + javax.servlet + + + + + + + + diff --git a/pom.xml b/pom.xml index c736343..fa8253b 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ Tool for Building web Apis SImpLy on top of sparql endpoints + parent basil