code: package eu.wdaqua.qanary.web;
  
  import java.io.*;
  import java.net.MalformedURLException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.UUID;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import eu.wdaqua.qanary.business.QanaryConfigurator;
  import eu.wdaqua.qanary.message.QanaryAvailableQuestions;
  import eu.wdaqua.qanary.message.QanaryQuestionCreated;
  import eu.wdaqua.qanary.message.QanaryQuestionInformation;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.core.io.FileSystemResource;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.ResponseEntity;
  import org.springframework.stereotype.Controller;
  import org.springframework.util.Assert;
  import org.springframework.web.HttpRequestMethodNotSupportedException;
  import org.springframework.web.bind.annotation.*;
  import org.springframework.web.context.request.RequestAttributes;
  import org.springframework.web.context.request.RequestContextHolder;
  import org.springframework.web.context.request.ServletRequestAttributes;
  import org.springframework.web.multipart.MultipartFile;
  
  import io.swagger.v3.oas.annotations.Operation;
  
  /**
   * controller for all service call w.r.t. questions
   *
   * @author AnBo
   */
  @Controller
  public class QanaryQuestionController {
  	private String directoryForStoringQuestionRawData;
  	private final String storedQuestionPrefix = "stored-question_";
  
  	private static final Logger logger = LoggerFactory.getLogger(QanaryQuestionController.class);
  
  	// Set this to allow browser requests from other websites
  	@ModelAttribute
  	public void setVaryResponseHeader(HttpServletResponse response) {
  		response.setHeader("Access-Control-Allow-Origin", "*");
  	}
  
  	/**
  	 * inject QanaryConfigurator
  	 */
  	@Autowired
  	public QanaryQuestionController(final QanaryConfigurator qanaryConfigurator,
  			final QanaryPipelineConfiguration myQanaryPipelineConfiguration) {
  		this.directoryForStoringQuestionRawData = myQanaryPipelineConfiguration.getQuestionsDirectory();
  	}
  
  	/**
  	 * synchronous call to start the QA process (POST), return the URL of the
  	 * created question
  	 */
  	@PostMapping(value = "/question", produces = "application/json")
  	@ResponseBody
  	@Operation(
  		summary = "Synchronous call to start a QA process", //
  		operationId = "createQuestion", //
  		description = "Start a question answering process via synchronous POST request "
  					+ "and return the URL of the created question. "
  					+ "Requires a question string."
  	)
  	public ResponseEntity<?> createQuestion(
  			@RequestParam(value = QanaryStandardWebParameters.QUESTION, required = true)
  			final String questionstring) {
  
  		logger.info("add received question: {}", questionstring);
  		QanaryQuestionCreated responseMessage;
  		try {
  			responseMessage = this.storeQuestion(questionstring);
  		} catch (IOException e) {
  			// will be caused by problems with file writing
  			e.printStackTrace();
  			return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
  		} catch (URISyntaxException e) {
  			// should be caused by a bad URI
  			e.printStackTrace();
  			return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
  		}
  
  		// send a HTTP 201 CREATED message back
  		return new ResponseEntity<QanaryQuestionCreated>(responseMessage, HttpStatus.CREATED);
  	}
  
  	public QanaryQuestionCreated storeQuestion(String questionstring) throws IOException, URISyntaxException {
  
  		// store the received question in a file
  		final String filename = storedQuestionPrefix+"_text_" + UUID.randomUUID().toString();
  		final String filepath = Paths.get(this.getDirectoryForStoringQuestionRawData(), filename).toString();
  
  		// check if question directory is actually existing
  		if (!Files.isDirectory(Paths.get(this.getDirectoryForStoringQuestionRawData()))) {
  			File file = new File(this.getDirectoryForStoringQuestionRawData());
  			file.mkdirs();
  			logger.warn("created directory for storing questions: {}", this.getDirectoryForStoringQuestionRawData());
  		} else {
  			logger.warn("directory exists: {}", this.getDirectoryForStoringQuestionRawData());
  		}
  
  		// Save the file locally
  		try (final BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(new File(filepath)))){
  			stream.write(questionstring.getBytes(StandardCharsets.UTF_8));
  		}
  
  		final URI uriOfQuestion = new URI(this.getHost() + "/question/" + filename);
  		logger.info("uriOfQuestion: {}", uriOfQuestion);
  
  		return new QanaryQuestionCreated(filename, uriOfQuestion);
  	}
  
  	/**
  	 * synchronous call to start the QA process (POST) with an audio file, return
  	 * the URL of the created question
  	 * @param file // TODO: why is the file parameter not shown in swagger-ui?
  	 */
  	@PostMapping(value = "/question_audio", produces = "application/json")
  	@ResponseBody
  	@Operation(
  		summary = "Synchronous call o sart a QA process wih an audio file", //
  		operationId = "createAudioQuestion", //
  		description = "Start a question answering process with an audio file via synchronous POST "
  					+ "and return the URL of the created question. "
  					+ "Requires an audio file."
  	)
  	public ResponseEntity<?> createAudioQuestion(
  			@RequestParam(value = QanaryStandardWebParameters.QUESTION, required = true) final MultipartFile file) {
  
  		logger.info("new audio file received: {}", file.getName());
  		QanaryQuestionCreated responseMessage;
  		try {
  			responseMessage = this.storeAudioQuestion(file);
  		} catch (IOException e) {
  			// will be caused by problems with file writing
  			e.printStackTrace();
  			return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
  		} catch (URISyntaxException e) {
  			// should be caused by a bad URI
  			e.printStackTrace();
  			return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
  		}
  
  		// send a HTTP 201 CREATED message back
  		return new ResponseEntity<QanaryQuestionCreated>(responseMessage, HttpStatus.CREATED);
  	}
  
  	public QanaryQuestionCreated storeAudioQuestion(MultipartFile file) throws IOException, URISyntaxException {
  
  		// store the received question in a file
  		final String filename = storedQuestionPrefix+"_audio_" + UUID.randomUUID().toString();
  		// check if question directory is actually existing
  		if (!Files.isDirectory(Paths.get(this.getDirectoryForStoringQuestionRawData()))) {
  			File dir = new File(this.getDirectoryForStoringQuestionRawData());
  			dir.mkdirs();
  			logger.warn("created directory for storing questions: {}", this.getDirectoryForStoringQuestionRawData());
  		} else {
  			logger.warn("directory exists: {}", this.getDirectoryForStoringQuestionRawData());
  		}
  
  		// Save the file locally
  		try {
  			if (file.isEmpty()) {
  				throw new IOException("Failed to store empty file " + file.getOriginalFilename());
  			}
  			Files.copy(file.getInputStream(), Paths.get(this.getDirectoryForStoringQuestionRawData(), filename));
  		} catch (IOException e) {
  			throw new IOException("Failed to store file " + file.getOriginalFilename(), e);
  		}
  
  		final URI uriOfQuestion = new URI(this.getHost() + "/question/" + filename);
  		logger.info("uriOfQuestion: {}", uriOfQuestion);
  
  		return new QanaryQuestionCreated(filename, uriOfQuestion);
  	}
  
  	/**
  	 * return directory where the questions are stored or saved if not existing,
  	 * then create the directory
  	 */
  	private String getDirectoryForStoringQuestionRawData() {
  		return this.directoryForStoringQuestionRawData;
  	}
  
  	/**
  	 * returns the host (scheme and authority) based on the current request
  	 *
  	 * host and port are not taken from application.properties because
  	 * this would not return the correct values for a containerized service
  	 */
  	private String getHost() {
  		try {
  			URI uri = getUriOfCurrentReqest();
  			return uri.getScheme() + "://" + uri.getAuthority();
  		} catch (NullPointerException | URISyntaxException e) {
  			logger.error("Current request uri could not be found!");
  			logger.debug(e.getMessage());
  		}
          assert false;
  		return null;
  	}
  
  	private URI getUriOfCurrentReqest() throws URISyntaxException {
  		HttpServletRequest request = getCurrentHttpServletRequest();
  		Assert.notNull(request, "No request was found!");
  		return new URI(request.getRequestURL().toString());
  	}
  
  	/**
  	 * static method to get HttpServletRequest of the current request
  	 */
  	public static HttpServletRequest getCurrentHttpServletRequest() {
  		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  		if (requestAttributes instanceof ServletRequestAttributes) {
  			return ((ServletRequestAttributes)requestAttributes).getRequest();
  		}
  		logger.debug("Method not called in context of HTTP request!");
  		return null;
  	}
  
  	/**
  	 * delete a question
  	 */
  	@DeleteMapping(value = "/question/{questionid}", produces = "application/json")
  	@ResponseBody
  	@Operation(
  		summary = "Delete a given question", //
  		operationId = "deleteQuestion", //
  		description = "(Not yet implemented) Requires the question ID."
  	) 
  	public String deleteQuestion(@PathVariable final String questionid) throws HttpRequestMethodNotSupportedException {
  		// TODO: please implement deletion of file if and only if they are in
  		// this question directory (security!)
  		throw new HttpRequestMethodNotSupportedException("not yet implemented");
  	}
  
  	/**
  	 * return links to all questions
  	 */
  	@GetMapping(value = "/question/")
  	@ResponseBody
  	@Operation(
  		summary = "Return links to all questions", //
  		operationId = "getQuestions",//
  		description = "No parameters are required" // 
  	)
  	public QanaryAvailableQuestions getQuestions() throws IOException {
  
  		final QanaryAvailableQuestions questions = new QanaryAvailableQuestions(
  				this.getDirectoryForStoringQuestionRawData(), this.getHost());
  
  		logger.debug("Number of available questions in '{}': {}", this.getDirectoryForStoringQuestionRawData(),
  				questions.getAvailableQuestions().size());
  
  		return questions;
  	}
  
  	/**
  	 * return links to all information of one given question
  	 */
  	@GetMapping(value = "/question/{questionid}")
  	@ResponseBody
  	@Operation(
  		summary = "Return links to all information of a given question", //
  		operationId = "getQuestion",//
  		description = "Specify the question with its ID. " //
  			+ "Example: \"/question/1\""
  	)
  	public QanaryQuestionInformation getQuestion(@PathVariable final String questionid) throws MalformedURLException {
  		return new QanaryQuestionInformation(questionid, this.getHost());
  	}
  
  	/**
  	 * question for a given ID, raw return of the data
  	 */
  	@GetMapping(value = "/question/{questionid}/raw", produces = "text/plain;charset=UTF-8")
  	@ResponseBody
  	@Operation(
  		summary = "Return raw data for a given question", //
  		operationId = "getQuestionRawData", //
  		description = "Requires the ID of the question. "
  					+ "Example: \"/question/1/raw\""
  	)
  	public FileSystemResource getQuestionRawData(@PathVariable final String questionid) {
  
  		// TODO: move outside of this class
  		final String filename = Paths.get(this.getDirectoryForStoringQuestionRawData(), questionid).toString();
  		return new FileSystemResource(filename);
  	}
  
  	/**
  	 * fetch the processing status of a given question
  	 */
  	@GetMapping(value = "/question/{question}/status", headers = "Accept=application/json", produces = {
  			"application/json;charset=UTF-8" })
  	@ResponseBody
  	@Operation(
  		summary = "Fetch the processing status of a given question", //
  		operationId = "getStatusOfQuestion", //
  		description = "(Not yet implemented) Requires the URL of the question."
  	)
  	public String getStatusOfQuestion(@PathVariable final URL questionuri) {
  		logger.warn("NOT IMPLEMENTED");
  		// TODO: fetch the processing status of the given question URL
  		// TODO: OR return the complete RDF object
  		return null;
  	}
  
  }
  