code: package eu.wdaqua.qanary.web;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.nio.charset.StandardCharsets;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import javax.servlet.http.HttpServletRequest;
  
  import org.apache.commons.lang.NotImplementedException;
  import org.apache.commons.lang3.exception.ExceptionUtils;
  import org.apache.jena.query.Query;
  import org.apache.jena.query.QueryFactory;
  import org.apache.jena.query.ResultSet;
  import org.apache.jena.query.ResultSetFormatter;
  import org.json.JSONException;
  import org.json.JSONObject;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.MediaType;
  import org.springframework.http.ResponseEntity;
  import org.springframework.stereotype.Controller;
  import org.springframework.util.MultiValueMap;
  import org.springframework.web.bind.annotation.CrossOrigin;
  import org.springframework.web.bind.annotation.GetMapping;
  import org.springframework.web.bind.annotation.PostMapping;
  import org.springframework.web.bind.annotation.RequestHeader;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.bind.annotation.RequestParam;
  import org.springframework.web.bind.annotation.ResponseBody;
  
  import eu.wdaqua.qanary.commons.triplestoreconnectors.QanaryTripleStoreConnector;
  import eu.wdaqua.qanary.exceptions.SparqlQueryFailed;
  
  /**
   * a proxy for the triplestore configured for being used for storing the Qanary
   * data implementing the SPARQL protocol (a subset)
   * 
   * it hides the actual SPARQL endpoint from the components, s.t., no credentials
   * etc. need to be shared
   * 
   * @author AnBo
   *
   */
  @CrossOrigin
  @Controller
  public class QanarySparqlProtocolController {
  	public final static String SPARQL_ENDPOINT = "sparql";
  	public final static String SPARQL_ENDPOINT_TESTER = "checktriplestoreconnection";
  	private QanaryTripleStoreConnector myQanaryTripleStoreConnector;
  	private static final Logger logger = LoggerFactory.getLogger(QanarySparqlProtocolController.class);
  
  	public QanarySparqlProtocolController(QanaryTripleStoreConnector myQanaryTripleStoreConnector) {
  		this.myQanaryTripleStoreConnector = myQanaryTripleStoreConnector;
  		checkTriplestoreConnection();
  	}
  
  	private QanaryTripleStoreConnector getQanaryTripleStoreConnector() {
  		return this.myQanaryTripleStoreConnector;
  	}
  
  	@GetMapping(value = "/" + SPARQL_ENDPOINT_TESTER, consumes = { "*/*" })
  	@ResponseBody
  	public ResponseEntity<String> checkTriplestoreConnection() {
  		String sparqlQuery = "SELECT * { GRAPH ?g { ?s ?p ?o  } } LIMIT 1";
  		logger.info("checkTriplestoreConnection: test with {}", sparqlQuery);
  		ResultSet result = null;
  		try {
  			result = this.getQanaryTripleStoreConnector().select(sparqlQuery);
  		} catch (SparqlQueryFailed e) {
  			e.printStackTrace();
  			String message = "SPARQL query for connection check failed";
  			logger.error("{}: {}", message, e.toString());
  			return ResponseEntity.internalServerError().body(message + ": " + e.toString());
  		}
  
  		if (result != null && result.hasNext()) {
  			String message = "Triplestore is accessible and returns triples.";
  			logger.info(message);
  			return ResponseEntity.ok().body(message);
  		} else {
  			String message = "Triplestore is accessible, but NO triples are returned (correctly initialized?).";
  			logger.warn(message);
  			return ResponseEntity.ok().body(message);
  		}
  	}
  
  	/**
  	 * returns a HTML page
  	 * 
  	 * @return
  	 */
  	@RequestMapping(value = "/" + SPARQL_ENDPOINT, produces = {MediaType.TEXT_HTML_VALUE}, consumes = {MediaType.ALL_VALUE})
  	public String getSparqlAsHTML() {
  		return "sparql";
  	}
  
  	
  	@GetMapping(value = "/" + SPARQL_ENDPOINT, produces = "application/sparql-results+json", consumes = { "*/*" })
  	@ResponseBody
  	public ResponseEntity<String> getSparqlAsJSON( //
  			@RequestHeader(value = "accept", required = false) String acceptHeader, //
  			@RequestParam(required = true, value = "query") String sparqlQuery //
  	) throws SparqlQueryFailed, JSONException {
  		logger.info("getSparqlAsJSON // accept-header: {}, SELECT query: {}", acceptHeader, sparqlQuery);
  
  		Query query = null;
  		try {			
  			query = QueryFactory.create(sparqlQuery);
  		} catch (Exception e) {
  			logger.error("SPARQL query could not be processed because of the error: {}\nfailed query:\n{}", e, sparqlQuery);
  			e.printStackTrace();
  			throw new SparqlQueryFailed(sparqlQuery, this.getQanaryTripleStoreConnector().getFullEndpointDescription(), e);
  		}
  
  		logger.info("ask:{}, select:{}, unknown:{}, query:{}", query.isAskType(), query.isSelectType(),
  				query.isUnknownType(), sparqlQuery);
  
  		// get result of SPARQL query from connected triplestore
  		if (query.isAskType()) {
  			Boolean result = this.getQanaryTripleStoreConnector().ask(sparqlQuery);
  			// create HTTP response
  			HttpHeaders headers = new HttpHeaders();
  			headers.set("Content-Type", "application/sparql-results+json");
  			JSONObject json = new JSONObject();
  			json.put("head", new JSONObject());
  			json.put("boolean", result);
  			logger.debug("JSON response for ASK query: {}", json.toString(0));
  			ResponseEntity<String> responseEntity = ResponseEntity.ok().headers(headers).body(json.toString(2));
  			return responseEntity;
  		} else if (query.isSelectType()) {
  			ResultSet result = this.getQanaryTripleStoreConnector().select(sparqlQuery);
  			// transform the JSON string
  			ByteArrayOutputStream myOutputStrean = new ByteArrayOutputStream();
  			ResultSetFormatter.outputAsJSON(myOutputStrean, result);
  			String resultAsJSON = myOutputStrean.toString();
  			logger.debug("resultAsJSON:\n{}", resultAsJSON);
  
  			// for safety only: check if the string is a convertible to JSON
  			@SuppressWarnings("unused")
  			JSONObject jsonObject;
  			try {
  				jsonObject = new JSONObject(resultAsJSON);
  			} catch (JSONException e) {
  				logger.error("Error while converting to JSONObject:\n{}", e.toString());
  				throw new SparqlQueryFailed(sparqlQuery + "has lead to " + resultAsJSON,
  						myQanaryTripleStoreConnector.getFullEndpointDescription(), e);
  			}
  
  			// create HTTP response
  			HttpHeaders headers = new HttpHeaders();
  			headers.set("Content-Type", "application/sparql-results+json");
  			ResponseEntity<String> responseEntity = ResponseEntity.ok().headers(headers).body(resultAsJSON);
  			logger.debug("responseEntity: {}", responseEntity.toString());
  			
  			if(jsonObject.has("results") && jsonObject.getJSONObject("results").has("bindings") ) {
  				logger.info("number of found results: {}", jsonObject.getJSONObject("results").getJSONArray("bindings").length());
  			} else {
  				logger.warn("number of found results: could not be identified: {}", jsonObject); 
  			}
  			return responseEntity;
  		} else {
  			throw new SparqlQueryFailed(sparqlQuery, "internal Qanary triplestore proxy",
  					new Exception("given SPARQL query was neither ASK nor SELECT query"));
  		}
  
  	}
  
  	@GetMapping(value = "/" + SPARQL_ENDPOINT, produces = { "application/sparql-results+xml",
  			MediaType.APPLICATION_XML_VALUE }, consumes = { "*/*" })
  	@ResponseBody
  	public ResponseEntity<Map<String, Object>> getSparqlAsXML( //
  			@RequestHeader(value = "accept", required = false) String acceptHeader, //
  			@RequestParam(required = false, defaultValue = "someValue", value = "query") String sparqlQuery //
  	) throws SparqlQueryFailed {
  		logger.warn("getSparqlAsXML // accept-header: {}, SELECT query: {}", acceptHeader, sparqlQuery);
  		throw new NotImplementedException();
  	}
  
  	/**
  	 * POST queries (UPDATE SPARQL queries)
  	 * 
  	 * @param r
  	 * @param headers
  	 * @param acceptHeader
  	 * @param body
  	 * @return
  	 * @throws SparqlQueryFailed
  	 */
  	@PostMapping(value = "/" + SPARQL_ENDPOINT, consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE,
  			"application/sparql-update" }, produces = { "application/sparql-results+xml",
  					MediaType.APPLICATION_XML_VALUE, "application/sparql-results+json", })
  	public ResponseEntity<?> postSparql( //
  			HttpServletRequest r, //
  			@RequestHeader Map<String, String> headers, //
  			@RequestHeader(value = "accept", required = false) String acceptHeader, //
  			@RequestParam MultiValueMap<String, String> body) throws SparqlQueryFailed {
  		logger.info("postSparql // accept-header: {}, body: {}", acceptHeader, body);
  		logger.debug("requestURL: {}, contentLength: {}, encoding: {}, contentType: {}", r.getRequestURL().toString(),
  				r.getContentLength(), r.getCharacterEncoding(), r.getContentType());
  		headers.forEach((key, value) -> {
  			logger.debug(String.format("Header '%s' = %s", key, value));
  		});
  
  		// get query from message body
  		String updateQuery = this.getUpdateQueryFromBody(r, body);
  		if (updateQuery != null) {
  			logger.debug("recognized SPARQL UPDATE query: {}", updateQuery);
  			logger.info("UPDATE query: {}", updateQuery);
  
  			// execute SPARQL query
  			try {
  				this.getQanaryTripleStoreConnector().update(updateQuery);
  			} catch (SparqlQueryFailed e) {
  				logger.error("UPDATE query failed: {}", ExceptionUtils.getStackTrace(e));
  				throw e;
  			}
  
  			// create response message, no header and no content
  			HttpHeaders headersResponse = new HttpHeaders();
  			ResponseEntity<?> responseEntity = ResponseEntity.noContent().headers(headersResponse).build();
  			logger.warn("responseEntity: {}", responseEntity.toString());
  
  			return responseEntity;
  		} else {
  			logger.info("redirect processing as it is no UPDATE query: {}", body);
  			try {
  				return this.getSparqlAsJSON(acceptHeader, body.get("query").get(0));
  			} catch (Exception e) {
  				try {
  					return this.getSparqlAsJSON(acceptHeader, this.getQueryFromBodyInputStream(r));
  				} catch (Exception e2) {
  					throw new SparqlQueryFailed("no query extractable", "internal Qanary triplestore proxy", e);
  				}
  			}
  		}
  	}
  
  	/**
  	 * catch all and log it
  	 * 
  	 * @param r
  	 * @param headers
  	 * @return
  	 */
  	@RequestMapping(value = "/" + SPARQL_ENDPOINT + "*", method = { RequestMethod.GET, RequestMethod.POST })
  	public ResponseEntity<Object> getAnythingelse(HttpServletRequest r, @RequestHeader Map<String, String> headers) {
  		logger.warn("getAnythingelse: {}: {}, {}", r.getMethod(), r.getRequestURL().toString(), r.getQueryString());
  		logger.warn("number of headers: {}", headers.keySet().toArray().length);
  		headers.forEach((key, value) -> {
  			logger.warn(String.format("Header '%s' = %s", key, value));
  		});
  
  		for (Iterator<String> iterator = r.getAttributeNames().asIterator(); iterator.hasNext();) {
  			String name = (String) iterator.next();
  			logger.warn("{}: {}", name, r.getAttribute(name));
  		}
  
  		Map<String, Object> values = new HashMap<>();
  		return new ResponseEntity<>(values, HttpStatus.NOT_FOUND);
  	}
  
  	/**
  	 * retrieve the body from the inputstream of an HttpServletRequest instance
  	 * 
  	 * @param r
  	 * @return
  	 * @throws SparqlQueryFailed
  	 */
  	private String getQueryFromBodyInputStream(HttpServletRequest r) throws SparqlQueryFailed {
  		try {
  			String extractedQuery = new String(r.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
  			logger.debug("extractedQuery: '{}'", extractedQuery);
  			if (extractedQuery == null || extractedQuery.isEmpty()) {
  				return null;
  			} else {
  				return extractedQuery;
  			}
  		} catch (IOException e) {
  			String message = "no query could be extracted from the message body";
  			logger.error("{}: {}", message, ExceptionUtils.getStackTrace(e));
  			throw new SparqlQueryFailed(message, "internal Qanary triplestore proxy", e);
  		}
  	}
  
  	/**
  	 * returns the an UPDATE query if present in the form-encoded body or the
  	 * inputstream of an HttpServletRequest instance
  	 * 
  	 * remark: returns null if the body contains a SELECT query
  	 * 
  	 * @param r
  	 * @param body
  	 * @return
  	 * @throws SparqlQueryFailed
  	 */
  	private String getUpdateQueryFromBody(HttpServletRequest r, MultiValueMap<String, String> body)
  			throws SparqlQueryFailed {
  		try {
  			String updateQuery = body.get("update").get(0);
  			if (updateQuery != null) {
  				return updateQuery;
  			} else {
  				String selectQuery = body.get("query").get(0);
  				if (selectQuery != null) {
  					// an UPDATE query is required, so return null if a SELECT query was found
  					return null;
  				} else {
  					return this.getQueryFromBodyInputStream(r);
  				}
  			}
  		} catch (Exception e) {
  			return this.getQueryFromBodyInputStream(r);
  		}
  	}
  }
  