Smalyshev has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/349119 )
Change subject: [WIP] [DNM] Add Mediawiki API service
......................................................................
[WIP] [DNM] Add Mediawiki API service
Mediaiwki API is described by a template, which lists
inputs and outputs, and is invoked via a service:
SERVICE wikibase:mwapi {
bd:serviceParam wikibase:api "Categories" .
bd:serviceParam :titles "Albert Einstein" .
bd:serviceParam :category ?category .
bd:serviceParam :title ?title .
}
Change-Id: If0aa5c213e197f6b20b55092680930eb82d1a0a2
Bug: T148245
---
M blazegraph/pom.xml
M
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
M
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/geo/GeoBoxService.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/ApiTemplate.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceCall.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceFactory.java
M dist/src/script/runBlazegraph.sh
M pom.xml
M tools/pom.xml
M tools/runBlazegraph.sh
A tools/src/test/resources/blazegraph/services.json
11 files changed, 713 insertions(+), 11 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/wikidata/query/rdf
refs/changes/19/349119/1
diff --git a/blazegraph/pom.xml b/blazegraph/pom.xml
index 0ea96cc..4470840 100644
--- a/blazegraph/pom.xml
+++ b/blazegraph/pom.xml
@@ -20,6 +20,14 @@
<dependencies>
<dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
<!-- Blazegraph needs http client to run services. -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
index a028935..e0012fc 100644
---
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
@@ -20,6 +20,7 @@
import org.wikidata.query.rdf.blazegraph.constraints.WikibaseNowBOp;
import org.wikidata.query.rdf.blazegraph.geo.GeoService;
import org.wikidata.query.rdf.blazegraph.label.LabelService;
+import org.wikidata.query.rdf.blazegraph.mwapi.MWApiServiceFactory;
import org.wikidata.query.rdf.common.uri.GeoSparql;
import org.wikidata.query.rdf.common.uri.OWL;
import org.wikidata.query.rdf.common.uri.Ontology;
@@ -83,6 +84,7 @@
reg.setWhitelistEnabled(true);
LabelService.register();
GeoService.register();
+ MWApiServiceFactory.register();
// Whitelist services we like by default
reg.addWhitelistURL(GASService.Options.SERVICE_KEY.toString());
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/geo/GeoBoxService.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/geo/GeoBoxService.java
index a0d4e8f..5d6198b 100644
---
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/geo/GeoBoxService.java
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/geo/GeoBoxService.java
@@ -272,7 +272,7 @@
*/
private final BigdataValueFactory vf;
- public GeoBoxServiceCall(BigdataServiceCall wrappedCall, TermNode east,
+ GeoBoxServiceCall(BigdataServiceCall wrappedCall, TermNode east,
TermNode west, AbstractTripleStore kb) {
this.wrappedCall = wrappedCall;
this.east = east;
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/ApiTemplate.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/ApiTemplate.java
new file mode 100644
index 0000000..9ac05eb
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/ApiTemplate.java
@@ -0,0 +1,203 @@
+package org.wikidata.query.rdf.blazegraph.mwapi;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.bigdata.bop.IVariable;
+import com.bigdata.bop.IVariableOrConstant;
+import com.bigdata.rdf.sparql.ast.TermNode;
+import com.bigdata.rdf.sparql.ast.eval.ServiceParams;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import static
org.wikidata.query.rdf.blazegraph.mwapi.MWApiServiceFactory.paramNameToURI;
+/**
+ * This class represents API template.
+ */
+public final class ApiTemplate {
+ /**
+ * Set of fixed API parameters.
+ */
+ private final Map<String, String> fixedParams = new HashMap<>();
+ /**
+ * Set of API parameters that should come from input vars.
+ * The value is the default.
+ */
+ private final Map<String, String> inputVars = new HashMap<>();
+ /**
+ * Set of API parameters that should be sent to output.
+ * The value is the XPath to find the value.
+ */
+ private final Map<String, String> outputVars = new HashMap<>();
+ /**
+ * XPath to result items.
+ */
+ private String items;
+ /**
+ * Hidden ctor.
+ * Use fromJSON() to create the object.
+ */
+ private ApiTemplate() {}
+
+ /**
+ * Create API template from JSON configuration.
+ * @param json
+ * @return
+ */
+ public static ApiTemplate fromJSON(JsonNode json) {
+ ApiTemplate template = new ApiTemplate();
+
+ // Parse input params
+ final JsonNode params = json.get("params");
+ for (String paramName: (Iterable<String>)() -> params.fieldNames()) {
+ if (template.fixedParams.containsKey(paramName)
+ || template.inputVars.containsKey(paramName)) {
+ throw new IllegalArgumentException(
+ "Repeated input parameter " + paramName);
+ }
+
+ JsonNode value = params.get(paramName);
+ // scalar value means fixed parameter
+ if (value.isValueNode()) {
+ template.fixedParams.put(paramName, value.asText());
+ }
+ // otherwise it's a parameter
+ // TODO: ignoring type for now
+ if (value.has("default")) {
+ template.inputVars.put(paramName,
value.get("default").asText());
+ } else {
+ template.inputVars.put(paramName, null);
+ }
+ }
+
+ // Parse output params
+ final JsonNode output = json.get("output");
+ template.items = output.get("items").asText();
+ final JsonNode vars = output.get("vars");
+ for (String paramName: (Iterable<String>)() -> vars.fieldNames()) {
+ if (template.inputVars.containsKey(paramName)) {
+ throw new IllegalArgumentException("Parameter " + paramName +
" declared as both input and output");
+ }
+ template.outputVars.put(paramName, vars.get(paramName).asText());
+ }
+
+ return template;
+ }
+
+ /**
+ * Get items XPath.
+ * @return
+ */
+ public String getItemsPath() {
+ return items;
+ }
+
+ /**
+ * Get call fixed parameters.
+ * @return
+ */
+ public Map<String, String> getFixedParams() {
+ return fixedParams;
+ }
+
+ /**
+ * Find default for this parameter.
+ * @param name
+ * @return Default value or null.
+ */
+ public String getInputDefault(String name) {
+ return inputVars.get(name);
+ }
+
+ /**
+ * Create list of bindings from input params to specific variables or
constants.
+ * @param serviceParams Specific invocation params.
+ * @return
+ */
+ public Map<String, IVariableOrConstant> getInputVars(final ServiceParams
serviceParams) {
+ Map<String, IVariableOrConstant> vars = new
HashMap<>(inputVars.size());
+
+ for (Map.Entry<String, String> entry : inputVars.entrySet()) {
+ TermNode var = serviceParams.get(paramNameToURI(entry.getKey()),
null);
+ if (var == null) {
+ if (entry.getValue() == null) {
+ // Param should have either binding or default
+ throw new IllegalArgumentException("Parameter " +
entry.getKey() + " must be bound");
+ }
+ // If var is null but we have a default, put null there,
service call will know
+ // how to handle it.
+ vars.put(entry.getKey(), null);
+ } else {
+ if (!var.isConstant() && !var.isVariable()) {
+ // Binding should be constant or var
+ throw new IllegalArgumentException("Parameter " +
entry.getKey() + " must be constant or variable");
+ }
+ vars.put(entry.getKey(), var.getValueExpression());
+ }
+ }
+ return vars;
+ }
+
+ /**
+ * Create map of output variables from template and service params.
+ * @param serviceParams Specific invocation params.
+ * @return
+ */
+ public List<OutputVariable> getOutputVars(final ServiceParams
serviceParams) {
+ List<OutputVariable> vars = new ArrayList<>(outputVars.size());
+ for (Map.Entry<String, String> entry : outputVars.entrySet()) {
+ TermNode varNode =
serviceParams.get(paramNameToURI(entry.getKey()), null);
+ if (varNode == null) {
+ // It's always OK to ignore output vars
+ continue;
+ }
+ if (!varNode.isVariable()) {
+ throw new IllegalArgumentException("Output parameter " +
entry.getKey() + " must be bound to a variable");
+ }
+ IVariable var = (IVariable)varNode.getValueExpression();
+ if (var.isAnonymous()) {
+ // Using anonymous is useless, but we'll let it pass.
+ continue;
+ }
+ vars.add(new OutputVariable(var, entry.getValue()));
+ }
+ return vars;
+ }
+
+ /**
+ * Variable in the output of the API.
+ */
+ public static class OutputVariable {
+ /**
+ * Original Blazegraph var.
+ */
+ private final IVariable var;
+ /**
+ * XPath expression to extract value from result.
+ */
+ private final String xpath;
+
+ public OutputVariable(IVariable var, String xpath) {
+ this.var = var;
+ this.xpath = xpath;
+ }
+
+ /**
+ * Get associated variable.
+ * @return
+ */
+ public IVariable getVar() {
+ return var;
+ }
+
+ /**
+ * Get associated variable name.
+ * @return
+ */
+ public String getName() {
+ return var.getName();
+ }
+ }
+
+}
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceCall.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceCall.java
new file mode 100644
index 0000000..7b0a453
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceCall.java
@@ -0,0 +1,292 @@
+package org.wikidata.query.rdf.blazegraph.mwapi;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openrdf.model.Literal;
+import org.openrdf.model.impl.LiteralImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.wikidata.query.rdf.blazegraph.mwapi.ApiTemplate.OutputVariable;
+
+import com.bigdata.bop.Constant;
+import com.bigdata.bop.IBindingSet;
+import com.bigdata.bop.IVariable;
+import com.bigdata.bop.IVariableOrConstant;
+import com.bigdata.rdf.internal.IV;
+import com.bigdata.rdf.internal.VTE;
+import com.bigdata.rdf.internal.impl.TermId;
+import com.bigdata.rdf.lexicon.LexiconRelation;
+import com.bigdata.rdf.sparql.ast.service.BigdataServiceCall;
+import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.MockIVReturningServiceCall;
+
+import cutthecrap.utils.striterators.ICloseableIterator;
+
+/**
+ * Instance of API service call.
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class MWApiServiceCall implements MockIVReturningServiceCall,
BigdataServiceCall {
+ private static final Logger log =
LoggerFactory.getLogger(MWApiServiceCall.class);
+ /**
+ * Service call template.
+ */
+ private final ApiTemplate template;
+ /**
+ * List of input variable bindings.
+ */
+ private final Map<String, IVariableOrConstant> inputVars;
+ /**
+ * List of output variable bindings.
+ */
+ private final List<OutputVariable> outputVars;
+ /**
+ * HTTP connection.
+ */
+ private final HttpClient client;
+ /**
+ * The LexiconRelation for the TripleStore we're working with.
+ */
+ private final LexiconRelation lexiconRelation;
+
+ MWApiServiceCall(ApiTemplate template,
+ Map<String, IVariableOrConstant> inputVars,
+ List<OutputVariable> outputVars,
+ HttpClient client,
+ LexiconRelation lexiconRelation
+ ) {
+ this.template = template;
+ this.inputVars = inputVars;
+ this.outputVars = outputVars;
+ this.client = client;
+ this.lexiconRelation = lexiconRelation;
+ }
+
+ @Override
+ public IServiceOptions getServiceOptions() {
+ return MWApiServiceFactory.SERVICE_OPTIONS;
+ }
+
+ @Override
+ public ICloseableIterator<IBindingSet> call(IBindingSet[] bindingSets)
+ throws Exception {
+ return new MultiSearchIterator(bindingSets);
+ }
+
+ /**
+ * Get HTTP request for this particular query & binding.
+ * @param binding
+ * @return
+ */
+ private Request getHttpRequest(IBindingSet binding) {
+ // FIXME: real endpoint URL
+ Request request =
client.newRequest("https://en.wikipedia.org/w/api.php");
+ request.method(HttpMethod.GET);
+ // Using XML for now to use XPath on responses
+ request.param("format", "xml");
+ // Add fixed params
+ for (Map.Entry<String, String> param :
template.getFixedParams().entrySet()) {
+ request.param(param.getKey(), param.getValue());
+ }
+ // Resolve variable params
+ for (Map.Entry<String, IVariableOrConstant> term :
inputVars.entrySet()) {
+ String value = null;
+ IV boundValue = null;
+ if (term != null) {
+ boundValue = (IV)term.getValue().get(binding);
+ }
+ if (boundValue == null) {
+ // try default
+ value = template.getInputDefault(term.getKey());
+ } else {
+ value = boundValue.stringValue();
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("Could not find binding for
parameter " + term.getKey());
+ }
+ request.param(term.getKey(), value);
+ }
+
+ return request;
+ }
+
+ @Override
+ public List<IVariable<IV>> getMockVariables() {
+ List<IVariable<IV>> externalVars = new LinkedList<IVariable<IV>>();
+ for (OutputVariable v: outputVars) {
+ externalVars.add(v.getVar());
+ }
+ return externalVars;
+ }
+
+ /**
+ * A chunk of calls to resolve labels.
+ */
+ private class MultiSearchIterator implements
ICloseableIterator<IBindingSet> {
+ /**
+ * Binding sets being resolved in this chunk.
+ */
+ private final IBindingSet[] bindingSets;
+ /**
+ * Has this chunk been closed?
+ */
+ private boolean closed;
+ /**
+ * Index of the next binding set to handle when next is next called.
+ */
+ private int i;
+ /**
+ * Current search result.
+ */
+ private SearchResultsIterator searchResult;
+
+ MultiSearchIterator(IBindingSet[] bindingSets) {
+ this.bindingSets = bindingSets;
+ searchResult = doNextSearch();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (closed || i >= bindingSets.length) {
+ return false;
+ }
+
+ if (searchResult == null) {
+ return false;
+ }
+
+ if (searchResult.hasNext()) {
+ return true;
+ }
+
+ searchResult = doNextSearch();
+ if (searchResult == null) {
+ return false;
+ } else {
+ return searchResult.hasNext();
+ }
+ }
+
+ /**
+ * Produce next search results iterator. Skips over empty results.
+ * @return Result iterator or null if no more results.
+ */
+ private SearchResultsIterator doNextSearch() {
+ // Just in case, double check
+ if (closed || bindingSets == null || i >= bindingSets.length) {
+ searchResult = null;
+ return null;
+ }
+ SearchResultsIterator result;
+ do {
+ IBindingSet binding = bindingSets[i++];
+ result = doSearchFromBinding(binding);
+ } while (!result.hasNext() && i < bindingSets.length);
+ if (result.hasNext()) {
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Execute search for one specific binding set.
+ * @param binding
+ * @return Search results iterator.
+ */
+ private SearchResultsIterator doSearchFromBinding(IBindingSet binding)
{
+ final Request req = getHttpRequest(binding);
+ log.info("REQUEST: " + req.getQuery());
+ for (OutputVariable var: outputVars) {
+ binding.set(var.getVar(), new Constant(mock("TEST")));
+ }
+ return new SearchResultsIterator(new IBindingSet[] {binding});
+ }
+
+ /**
+ * Build a mock IV from a literal string.
+ */
+ private IV mock(String literalString) {
+ Literal literal = new LiteralImpl(literalString);
+ TermId mock = TermId.mockIV(VTE.LITERAL);
+ mock.setValue(lexiconRelation.getValueFactory().asValue(literal));
+ return mock;
+ }
+
+ @Override
+ public IBindingSet next() {
+ if (closed || i >= bindingSets.length || searchResult == null) {
+ return null;
+ }
+
+ if (searchResult.hasNext()) {
+ return searchResult.next();
+ }
+
+ searchResult = doNextSearch();
+ if (searchResult == null || !searchResult.hasNext()) {
+ return null;
+ } else {
+ return searchResult.next();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ }
+
+ /**
+ * Simple iterator for search results.
+ */
+ private class SearchResultsIterator implements Iterator<IBindingSet> {
+ /**
+ * The index into the array of hits wrapped by this iterator.
+ */
+ private int i;
+ /**
+ * The array of hits wrapped by this iterator.
+ */
+ private final IBindingSet[] results;
+
+ SearchResultsIterator(final IBindingSet[] results) {
+ if (results == null) {
+ throw new IllegalArgumentException("Null result?");
+ }
+
+ this.results = results;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return i < results.length;
+ }
+
+ @Override
+ public IBindingSet next() {
+ return results[i++];
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "SearchResultsIterator{nhits=" + results.length + "} : " +
results;
+ }
+ }
+}
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceFactory.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceFactory.java
new file mode 100644
index 0000000..f22b3a4
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/mwapi/MWApiServiceFactory.java
@@ -0,0 +1,153 @@
+package org.wikidata.query.rdf.blazegraph.mwapi;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.openrdf.model.URI;
+import org.openrdf.model.impl.URIImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.wikidata.query.rdf.common.uri.Ontology;
+
+import com.bigdata.rdf.sparql.ast.eval.AbstractServiceFactory;
+import com.bigdata.rdf.sparql.ast.eval.ServiceParams;
+import com.bigdata.rdf.sparql.ast.service.BigdataNativeServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.BigdataServiceCall;
+import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.ServiceCallCreateParams;
+import com.bigdata.rdf.sparql.ast.service.ServiceNode;
+import com.bigdata.rdf.sparql.ast.service.ServiceRegistry;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Service factory for calling out to Mediawiki API Services.
+ * Service call looks like:
+ *
+ * SERVICE wikibase:mwapi {
+ * bd:serviceParam wikibase:api "Categories" .
+ * bd:serviceParam :titles "Albert Einstein" .
+ * bd:serviceParam :category ?category .
+ * bd:serviceParam :title ?title .
+ * }
+ */
+public class MWApiServiceFactory extends AbstractServiceFactory {
+ private static final Logger log =
LoggerFactory.getLogger(MWApiServiceFactory.class);
+
+ /**
+ * Options configuring this service as a native Blazegraph service.
+ */
+ public static final BigdataNativeServiceOptions SERVICE_OPTIONS = new
BigdataNativeServiceOptions();
+ /**
+ * Service config.
+ */
+ private final Map<String, ApiTemplate> config = new HashMap<>();
+ /**
+ * The URI service key.
+ */
+ public static final URI SERVICE_KEY = new URIImpl(Ontology.NAMESPACE +
"mwapi");
+ /**
+ * API type parameter name.
+ */
+ public static final URI API_KEY = new URIImpl(Ontology.NAMESPACE + "api");
+ /**
+ * Default service config filename.
+ */
+ public static final String CONFIG_DEFAULT = "mwservices.json";
+ /**
+ * Config file parameter.
+ */
+ public static final String CONFIG_NAME =
MWApiServiceFactory.class.getName() + ".config";
+ /**
+ * Filename of the config.
+ */
+ public static final String CONFIG_FILE = System.getProperty(CONFIG_NAME,
CONFIG_DEFAULT);
+
+ public MWApiServiceFactory() throws IOException {
+ log.info("Loading MWAPI service configuration from " + CONFIG_FILE);
+ loadJSONConfig(new InputStreamReader(new FileInputStream(CONFIG_FILE),
StandardCharsets.UTF_8));
+ log.info("Registered " + config.size() + " services.");
+ }
+
+ /**
+ * @throws IOException
+ * Load config from JSON object.
+ * @param configReader
+ */
+ public void loadJSONConfig(Reader configReader) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readTree(IOUtils.toString(configReader));
+ for (String serviceName: (Iterable<String>)() -> node.fieldNames()) {
+ config.put(serviceName,
ApiTemplate.fromJSON(node.get(serviceName)));
+ }
+ }
+
+ @Override
+ public IServiceOptions getServiceOptions() {
+ return SERVICE_OPTIONS;
+ }
+
+ /**
+ * Register the service so it is recognized by Blazegraph.
+ */
+ public static void register() {
+ final ServiceRegistry reg = ServiceRegistry.getInstance();
+ try {
+ reg.add(SERVICE_KEY, new MWApiServiceFactory());
+ } catch (IOException e) {
+ // Do not add to whitelist if init failed.
+ log.warn("MW Service registration failed: " + e);
+ return;
+ }
+ reg.addWhitelistURL(SERVICE_KEY.toString());
+ }
+
+ @Override
+ public BigdataServiceCall create(ServiceCallCreateParams params, final
ServiceParams serviceParams) {
+ final ServiceNode serviceNode = params.getServiceNode();
+
+ if (serviceNode == null) {
+ throw new IllegalArgumentException();
+ }
+
+ final ApiTemplate template = getServiceTemplate(serviceParams);
+
+ return new MWApiServiceCall(template,
+ template.getInputVars(serviceParams),
+ template.getOutputVars(serviceParams),
+ params.getClientConnectionManager(),
+ params.getTripleStore().getLexiconRelation()
+ );
+ }
+
+ /**
+ * Extract service template name from params.
+ * @param serviceParams
+ * @return Service template
+ */
+ private ApiTemplate getServiceTemplate(final ServiceParams serviceParams) {
+ final String templateName = serviceParams.getAsString(API_KEY);
+ serviceParams.clear(API_KEY);
+ if (!config.containsKey(templateName)) {
+ throw new IllegalArgumentException(
+ "Service name " + templateName + " not found in
configuration");
+ }
+ return config.get(templateName);
+ }
+
+ /**
+ * Create predicate parameter URI from name.
+ * TODO: for now, identical to name, check if that works.
+ * @param name
+ * @return
+ */
+ public static URI paramNameToURI(String name) {
+ return new URIImpl(name);
+ }
+}
diff --git a/dist/src/script/runBlazegraph.sh b/dist/src/script/runBlazegraph.sh
index 3027837..6cd7419 100755
--- a/dist/src/script/runBlazegraph.sh
+++ b/dist/src/script/runBlazegraph.sh
@@ -62,6 +62,7 @@
-Dorg.wikidata.query.rdf.blazegraph.inline.literal.WKTSerializer.noGlobe=$DEFAULT_GLOBE
\
-Dcom.bigdata.rdf.sail.webapp.client.RemoteRepository.maxRequestURLLength=7168 \
-Dcom.bigdata.rdf.sail.sparql.PrefixDeclProcessor.additionalDeclsFile=$DIR/prefixes.conf
\
+
-Dorg.wikidata.query.rdf.blazegraph.mwapi.MWApiServiceFactory.config=$DIR/mwservices.json
\
-Dcom.bigdata.rdf.sail.webapp.client.HttpClientConfigurator=org.wikidata.query.rdf.blazegraph.ProxiedHttpConnectionFactory
\
-Dhttp.userAgent="${USER_AGENT}" \
${BLAZEGRAPH_OPTS} \
diff --git a/pom.xml b/pom.xml
index ad20dae..657f964 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,7 @@
<properties>
<blazegraph.version>2.1.5-SNAPSHOT</blazegraph.version>
+ <jackson.version>2.2.3</jackson.version>
<jetty.version>9.2.3.v20140905</jetty.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sesame.version>2.7.12</sesame.version>
@@ -99,6 +100,21 @@
<type>pom</type>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
diff --git a/tools/pom.xml b/tools/pom.xml
index a55ea6d..b13d3aa 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -263,24 +263,28 @@
<value>org.wikidata.query.rdf.blazegraph.WikibaseOptimizers</value>
</property>
<property>
-
<name>org.wikidata.query.rdf.blazegraph.inline.literal.WKTSerializer.noGlobe</name>
- <value>2</value>
+
<name>org.wikidata.query.rdf.blazegraph.inline.literal.WKTSerializer.noGlobe</name>
+ <value>2</value>
</property>
<property>
-
<name>com.bigdata.rdf.sail.sparql.PrefixDeclProcessor.additionalDeclsFile</name>
-
<value>${project.parent.basedir}/dist/src/script/prefixes.conf</value>
+
<name>com.bigdata.rdf.sail.sparql.PrefixDeclProcessor.additionalDeclsFile</name>
+
<value>${project.parent.basedir}/dist/src/script/prefixes.conf</value>
</property>
<property>
-
<name>com.bigdata.rdf.sail.webapp.client.HttpClientConfigurator</name>
-
<value>org.wikidata.query.rdf.blazegraph.ProxiedHttpConnectionFactory</value>
+
<name>com.bigdata.rdf.sail.webapp.client.HttpClientConfigurator</name>
+
<value>org.wikidata.query.rdf.blazegraph.ProxiedHttpConnectionFactory</value>
</property>
<property>
- <name>http.userAgent</name>
- <value>Wikidata Query Service (testing);
https://query.wikidata.org/</value>
+ <name>http.userAgent</name>
+ <value>Wikidata Query Service (testing);
https://query.wikidata.org/</value>
</property>
<property>
- <name>wikibaseServiceWhitelist</name>
-
<value>${project.basedir}/src/test/resources/blazegraph/whitelist.txt</value>
+ <name>wikibaseServiceWhitelist</name>
+
<value>${project.basedir}/src/test/resources/blazegraph/whitelist.txt</value>
+ </property>
+ <property>
+
<name>org.wikidata.query.rdf.blazegraph.mwapi.MWApiServiceFactory.config</name>
+
<value>${project.basedir}/src/test/resources/blazegraph/services.json</value>
</property>
</properties>
</configuration>
diff --git a/tools/runBlazegraph.sh b/tools/runBlazegraph.sh
index d02e41d..b9e9346 100755
--- a/tools/runBlazegraph.sh
+++ b/tools/runBlazegraph.sh
@@ -1,3 +1,4 @@
#!/bin/bash
+export MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE
-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
mvn -PrunBlazegraph generate-sources
diff --git a/tools/src/test/resources/blazegraph/services.json
b/tools/src/test/resources/blazegraph/services.json
new file mode 100644
index 0000000..b4a93e5
--- /dev/null
+++ b/tools/src/test/resources/blazegraph/services.json
@@ -0,0 +1,22 @@
+{
+ "Categories": {
+ "params": {
+ "action": "query",
+ "prop": "categories",
+ "titles": {
+ "type": "list"
+ },
+ "cllimit": {
+ "type": "int",
+ "default": 500
+ }
+ },
+ "output": {
+ "items": "//api/query/pages/page/categories",
+ "vars": {
+ "category": "@title",
+ "title": "//api/query/pages/page/@title"
+ }
+ }
+ }
+}
\ No newline at end of file
--
To view, visit https://gerrit.wikimedia.org/r/349119
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If0aa5c213e197f6b20b55092680930eb82d1a0a2
Gerrit-PatchSet: 1
Gerrit-Project: wikidata/query/rdf
Gerrit-Branch: master
Gerrit-Owner: Smalyshev <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits