package eu.geoknow.generator.rest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import eu.geoknow.generator.configuration.FrameworkConfiguration;
import eu.geoknow.generator.exceptions.InformationMissingException;
import eu.geoknow.generator.exceptions.SPARQLEndpointException;
import eu.geoknow.generator.publish.DataHandler;
import eu.geoknow.generator.publish.PublishingConfiguration;
import eu.geoknow.generator.users.UserManager;
import eu.geoknow.generator.users.UserManager.GraphPermissions;
import eu.geoknow.generator.users.VirtuosoUserManager;
import eu.geoknow.generator.utils.Utils;
/**
* REST API that allows for publishing RDF data from one or more input graphs into one target graph.
*
* @author mvoigt
*
*/
@Path("/publish")
public class Publish {
private static Logger logger = Logger.getLogger(Publish.class);
/**
* Method to publish RDF data with a given target graph
*
* @param endpointUri URI of the SPARQL enpoint with the input graphs and the target graph
* @param targetGraphUri URI of the target graph
* @param inputArray JSON array with the input graphs (key: graph) and a flag (key: delete) if the
* graph should be dropped after publishing or not. <br>
* Example: [{"graph":"http://ex.com/ng1","delete": "true"},
* {"graph":"http://ex.com/ng2","delete": "false"}]
* @param backupExistingData String with boolean values "true" or "false". If yes, the existing
* data in the tragetGraphUri will be versioned
* @param meta URL-encoded String with RDF triples to add as meta data
* @param userName user name of the graphs owner in the workbench
* @return
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
public Response publish(@FormParam("endpointUri") String endpointUri,
@FormParam("targetGraphUri") String targetGraphUri,
@FormParam("inputGraphArray") String inputArray,
@FormParam("backupExistingData") String backupExistingData,
@FormParam("metaRdf") String meta, @FormParam("userName") String userName) {
// create config
PublishingConfiguration config;
try {
// parse JSON array, [{"graph":"http://ex.com/ng1","delete":
// "true"},
// {"graph":"http://ex.com/ng2","delete": "false"}]
JsonArray entries = (JsonArray) new JsonParser().parse(inputArray);
HashMap<String, Boolean> input = new HashMap<String, Boolean>();
for (int i = 0; i < entries.size(); i++) {
JsonObject elem = (JsonObject) entries.get(i);
JsonElement graph = elem.get("graph");
JsonElement delFlag = elem.get("delete");
if (graph == null || delFlag == null) {
throw new InformationMissingException("The input graph array contains erros.");
}
logger.info("JSON pair: " + graph.getAsString() + " - " + delFlag.getAsString());
input.put(graph.getAsString(), Boolean.parseBoolean(delFlag.getAsString()));
}
// if no input is provided, throw exception
if (input.isEmpty()) {
throw new InformationMissingException("No input graphs provided.");
}
// check if meta data is provided. if so, create Jena model from
// string
Model metaData = ModelFactory.createDefaultModel();
if (!Utils.isNullOrEmpty(meta)) {
metaData.read(new ByteArrayInputStream(meta.getBytes()), null, "TURTLE");
}
// finally, create config object
config =
new PublishingConfiguration(endpointUri, input, targetGraphUri,
Boolean.parseBoolean(backupExistingData), metaData, userName);
} catch (InformationMissingException e) {
logger.error(e);
return Response.status(Response.Status.BAD_REQUEST)
.entity("{\"status\" : \"" + e.getMessage() + "\"}").build();
} catch (Exception e) {
logger.error(e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("{\"status\" : \"" + e.getMessage() + "\"}").build();
}
// if the config is initialized, call the publishing pipeline
try {
DataHandler dh = new DataHandler(config);
dh.publishData();
} catch (Exception e) {
e.printStackTrace();
logger.error(e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("{\"status\" : \"" + e.getMessage() + "\"}").build();
}
String json =
"{\"status\" : \"Data successfully published in target graph <"
+ config.getTargetGraphUri() + ">\"}";
return Response.status(Response.Status.OK).entity(json).type(MediaType.APPLICATION_JSON)
.build();
}
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/createTempGraph")
public Response createTempGraph(@FormParam("endpointUri") String endpointUri,
@FormParam("tempGraphUri") String tempGraphUri, @FormParam("userName") String userName) {
String rdfStoreUser;
UserManager rdfStoreUserManager;
try {
rdfStoreUserManager = FrameworkConfiguration.getInstance().getRdfStoreUserManager();
rdfStoreUserManager.setRdfGraphPermissions(userName, tempGraphUri, GraphPermissions.WRITE);
((VirtuosoUserManager) rdfStoreUserManager).grantLOLook(userName);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// String query = "CREATE SILENT GRAPH <" + tempGraphUri + ">";
try {
String response =
FrameworkConfiguration.getInstance().getSystemRdfStoreManager().createGraph(tempGraphUri);
// check if it worked
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(response);
Iterator<JsonNode> bindingsIter = rootNode.path("results").path("bindings").elements();
if (bindingsIter.hasNext()) {
JsonNode bindingNode = bindingsIter.next();
if (bindingNode.get("callret-0").path("value").textValue().contains("done")) {
logger.info("temp graph <" + tempGraphUri + "> created.");
} else {
throw new SPARQLEndpointException("Creating the temp graph <" + tempGraphUri + "> fails.");
}
}
} catch (Exception e) {
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
String json = "{\"status\" : \"Temporary graph successfully created.\"}";
return Response.status(Response.Status.OK).entity(json).type(MediaType.APPLICATION_JSON)
.build();
}
/**
* A helper method to wait in the process chain until the D2RQ task is finished
*
* TODO this functionality should be removed after an update of the D2RQ service
*
* @param serviceUri the D2RQ service URI to call, which includes the task ID
* (/tasks/{id}/metadata/get)
* @return
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/waitTillD2rqTaskIsDone")
public Response waitTillD2rqTaskIsDone(@FormParam("serviceUri") String serviceUri) {
// is the URI is missing, return an error
if (Utils.isNullOrEmpty(serviceUri)) {
String json = "{\"status\" : \"Service URI is missing.\"}";
return Response.status(Response.Status.BAD_REQUEST).entity(json)
.type(MediaType.APPLICATION_JSON).build();
}
// track the time
long start = System.currentTimeMillis();
/*
* Task Metadata { "id" : 1234, "name" : "persons", "created" : "2013-06-12T13:49:02.656Z",
* "compositeMapping" : 543, "dataset" : 324, "owner" : "test", "started" :
* "2013-06-15T13:49:02.656Z", "done" : "2013-06-15T13:59:02.656Z", "status" : "DONE",
* "description" : null, "rdfFilePath" : "c://d2rq/rdf/persons.rdf", "compositeMappingName" :
* "persons", "httpEndpoint" : "http://sparql", "graph" : "http://persons/graph" }
*/
// add empty query params
ArrayList<NameValuePair> postParameters = new ArrayList<NameValuePair>();
boolean isFinished = false;
try {
// call service again and again until the task is DONE
while (!isFinished) {
String jsonResponse = executeHttpPostQuery(serviceUri, postParameters);
// check response
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonResponse);
JsonNode status = rootNode.get("status");
if ("DONE".equals(status.textValue().toUpperCase())) {
// leave while statement
isFinished = true;
} else {
// wait a short while: 2s
Thread.sleep(2000);
}
}
} catch (Exception e) {
// catch any error and return 500
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
long end = System.currentTimeMillis();
long duration = end - start;
logger.info("D2RQ task is finished in at least " + duration + "ms.");
// return after d2RQ task finished
String json = "{\"status\" : \"D2RQ task successfully finished in about " + duration + "ms.\"}";
return Response.status(Response.Status.OK).entity(json).type(MediaType.APPLICATION_JSON)
.build();
}
/**
* Method to call the SPARQL endpoint and to send the given query as HTTP POST. It returns the
* response body as string.
*
* This should be moved later to the new graph REST API
*
* @param query the query to POST
* @param endpointUri the URi to call
* @return the response text
* @throws ClientProtocolException
* @throws IOException
*/
private static String executeSparqlQuery(String query, String endpointUri)
throws ClientProtocolException, IOException {
// create query params
// TODO double check if it the same for OntoQuad!
ArrayList<NameValuePair> postParameters = new ArrayList<NameValuePair>();
postParameters.add(new BasicNameValuePair("query", query));
postParameters.add(new BasicNameValuePair("format", "application/sparql-results+json"));
// call service
return executeHttpPostQuery(endpointUri, postParameters);
}
/**
* Method to call a service endpoint using a POST request. It returns the response body as string.
*
* This should be moved later to the new graph REST API
*
* @param endpointUri the URI to call
* @param postParameters list of post parameters, could be empty but not null
* @return the response text
* @throws ClientProtocolException
* @throws IOException
*/
private static String executeHttpPostQuery(String endpointUri,
ArrayList<NameValuePair> postParameters) throws ClientProtocolException, IOException {
HttpPost request = new HttpPost(endpointUri);
// use UTF8!
request.setEntity(new UrlEncodedFormEntity(postParameters, "UTF-8"));
// create HTTP client
CloseableHttpClient httpClient = HttpClients.createDefault();
// call
final CloseableHttpResponse response = httpClient.execute(request);
// TODO check status code and return exception on error
logger.info("Response code of the query against the server: "
+ response.getStatusLine().getStatusCode());
// read data and return the response string
BufferedReader in =
new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuilder builder = new StringBuilder();
for (String line = null; (line = in.readLine()) != null;) {
builder.append(line).append("\n");
}
in.close();
httpClient.close();
return builder.toString();
}
}