package eu.dnetlib.ariadneplus.virtuoso; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import eu.dnetlib.ariadneplus.CRM; import eu.dnetlib.ariadneplus.publisher.AriadnePlusPublisherException; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.NameValuePair; 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.http.util.EntityUtils; import org.apache.jena.query.ResultSet; import org.apache.jena.sparql.engine.http.QueryEngineHTTP; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; /** * Created by Alessia Bardi on 31/01/2018. * Read-only API for virtuoso. * * //TODO: error handling (http://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services) * //TODO: pagination * //TODO swagger documentation? * * @author Alessia Bardi */ @RestController public class VirtuosoReadAPI { private static final Log log = LogFactory.getLog(VirtuosoReadAPI.class); @Value("${virtuoso.sparqlurl}") private String sparqlUrl; @Value("${virtuoso.pwd}") private String username; @Value("${virtuoso.pwd}") private String password; @Value("${virtuoso.uri.base.default}") private String defaultBaseURI; @Value("${virtuoso.connectionstring") private String virtuosoConnectionString; @Autowired private Configuration freemarkerConfig; @RequestMapping(value = "/virtuoso/apiSubjectsWithType", produces = { "application/json" }, method = RequestMethod.GET) public List getSubjectsForApiWithType(@RequestParam final String api, @RequestParam final String typeNamespace, @RequestParam final String typeName, @RequestParam final int limit, @RequestParam final int offset){ String fullTypeName = typeNamespace + typeName; log.debug(String.format("Getting subjects of type %s for API %s limit %d offset %d", fullTypeName, api, limit, offset)); //if I add the ORDER BY the query is too slow: let's hope we are not getting the same subjects over and over again String queryForSubjectsTemplate = "DEFINE input:inference 'ariadneplus_rules' SELECT DISTINCT ?s WHERE { GRAPH ?g {?s a <%s> .} . GRAPH dnet:graph {?g dnet:collectedFrom <%s> .}} LIMIT %d OFFSET %d"; String q = String.format(queryForSubjectsTemplate, fullTypeName, defaultBaseURI+api, limit, offset); log.debug("SPARQL query: "+q); final QueryEngineHTTP serviceRequest = new QueryEngineHTTP(sparqlUrl, q); ResultSet subjects = serviceRequest.execSelect(); Iterator s = Iterators.transform(subjects, qs -> qs.getResource("s").getURI()); List res = Lists.newArrayList(s); serviceRequest.close(); return res; } /** * Returns the paginated list of resource URI used as subjects in the context of a given api. All subject URIs are returned, except from CRM.E55_Type and CRM.E41_Appellation. * TODO: probably we can remove this, as it is used only for testing. * @param api * @param limit * @param offset * @return a page of URIs that are subjects in the context of a given api * */ @Deprecated @RequestMapping(value = "/virtuoso/apiSubjects", produces = { "application/json" }, method = RequestMethod.GET) public List getSubjectsForApi(@RequestParam final String api, @RequestParam final int limit, @RequestParam final int offset){ log.debug(String.format("Getting subjects for API %s, limit %d offset %d", api, limit, offset)); //if I add the ORDER BY the query is too slow: let's hope we are not getting the same subjects over and over again String queryForSubjectsTemplate = "DEFINE input:inference 'ariadneplus_rules' SELECT DISTINCT ?s WHERE { GRAPH ?g {?s a ?t . FILTER (?t != <%s> && ?t != <%s>)} . GRAPH dnet:graph {?g dnet:collectedFrom <%s> .}} LIMIT %d OFFSET %d "; String q = String.format(queryForSubjectsTemplate, CRM.E55_Type.getURI(), CRM.E41_Appellation.getURI(), defaultBaseURI+api, limit, offset); log.debug("SPARQL query: "+q); final QueryEngineHTTP serviceRequest = new QueryEngineHTTP(sparqlUrl, q); ResultSet subjects = serviceRequest.execSelect(); Iterator s = Iterators.transform(subjects, qs -> qs.getResource("s").getURI()); List res = Lists.newArrayList(s); serviceRequest.close(); return res; } @RequestMapping(value = "/virtuoso/subject", produces = { "application/rdf+xml", "application/xml" }, method = RequestMethod.GET) @ResponseStatus(value = HttpStatus.OK) public void getSubject(@RequestParam final String subjectURL, @RequestParam final String typeName, @RequestParam(name="timeout") final String timeoutMs, final OutputStream responseStream) throws IOException, TemplateException, AriadnePlusPublisherException { String templateName = typeName+".sparql"; Template temp = freemarkerConfig.getTemplate(templateName); Map values = new HashMap<>(); values.put("subjectURL", subjectURL); StringWriter sw = new StringWriter(); temp.process(values, sw); String q = sw.toString(); log.debug("Querying for "+subjectURL+" with query "+templateName); sendConstructResponse(q, timeoutMs, responseStream); } protected void sendConstructResponse(final String query, final String timeoutMs, final OutputStream responseStream) throws IOException, AriadnePlusPublisherException { String res = executeSparqlPost(query, timeoutMs); IOUtils.write(res, responseStream); } protected String executeSparqlPost(final String query, final String timeoutMs) throws IOException, AriadnePlusPublisherException { try (CloseableHttpClient httpclient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(sparqlUrl); httpPost.setHeader(HttpHeaders.ACCEPT, "application/rdf+xml"); List nvps = Lists.newArrayList(); nvps.add(new BasicNameValuePair("query", query)); if (StringUtils.isNotBlank(timeoutMs)) { nvps.add(new BasicNameValuePair("timeout", timeoutMs)); } httpPost.setEntity(new UrlEncodedFormEntity(nvps)); try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) { HttpEntity entity2 = response2.getEntity(); String res = IOUtils.toString(entity2.getContent()); EntityUtils.consume(entity2); int statusCode = response2.getStatusLine().getStatusCode(); switch(statusCode){ case 200: return res; case 504: String msg = String.format("ERROR 504 on query %s", query); throw new AriadnePlusPublisherException(msg); default: String errorMessage = String.format("ERROR HTTP STATUS CODE %d, REASON PHRASE: %s\n ERROR BODY: %s", statusCode, response2.getStatusLine(), res); log.error(errorMessage); return ""; //throw new AriadnePlusPublisherException(errorMessage); } } } } }