org.trec.liveqa.TrecLiveQaDemoServer.java Source code

Java tutorial

Introduction

Here is the source code for org.trec.liveqa.TrecLiveQaDemoServer.java

Source

package org.trec.liveqa;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.FileHandler;
import java.util.logging.Level;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import fi.iki.elonen.NanoHTTPD;

import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.ByteBuffer;
import java.net.InetSocketAddress;
import java.io.*;
import java.util.concurrent.TimeUnit;

import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.lang.InterruptedException;
import java.util.concurrent.ExecutionException;
import java.net.SocketAddress;

import java.util.Arrays;

import org.json.*;

/**
 * Copyright 2015 Yahoo Inc.<br>
 * Licensed under the terms of the MIT license. Please see LICENSE file at the root of this project for terms.
 * <p/>
 * Sample server-side application for 2015 TREC LiveQA challenge.<br>
 * Usage: TrecLiveQaDemoServer [port-id]<br>
 * Stops on any input.
 * 
 * @author yuvalp@yahoo-inc.com
 * 
 */

/*The server that is responsible for sending the incoming question to the Python client and receive the answer*/

public class TrecLiveQaDemoServer extends NanoHTTPD {

    public static final String LOG_FILENAME = "POSTED_QUESTIONS.txt";
    public static final String PARTICIPANT_ID = "uwaterlooclarke";

    public static final String QUESTION_ID_PARAMETER_NAME = "qid";
    public static final String QUESTION_TITLE_PARAMETER_NAME = "title";
    public static final String QUESTION_BODY_PARAMETER_NAME = "body";
    public static final String QUESTION_CATEGORY_PARAMETER_NAME = "category";

    public static final String ANSWER_ROOT_ELEMENT_NAME = "xml";
    public static final String ANSWER_BASE_ELEMENT_NAME = "answer";
    public static final String ANSWER_PARTICIPANT_ID_ATTRIBUTE_NAME = "pid";
    public static final String ANSWER_ANSWERED_YES_NO_ATTRIBUTE_NAME = "answered";
    public static final String ANSWER_REPORTED_TIME_MILLISECONDS_ATTRIBUTE_NAME = "time";
    public static final String ANSWER_WHY_NOT_ANSWERED_ELEMENT_NAME = "discard-reason";
    public static final String ANSWER_CONTENT_ELEMENT_NAME = "content";
    public static final String ANSWER_RESOURCES_ELEMENT_NAME = "resources";
    public static final String RESOURCES_LIST_SEPARATOR = ",";
    public static final String NO_ANSWER = "no answer";

    public static final String YES = "yes";
    public static final String NO = "no";

    public static final String EXCUSE = "I just couldn't cut it :(";

    public static final int DEFAULT_PORT = 11000;
    public static final Locale WORKING_LOCALE = Locale.US;
    public static final String WORKING_TIME_ZONE_ID = "UTC";
    public static final TimeZone WORKING_TIME_ZONE = TimeZone.getTimeZone(WORKING_TIME_ZONE_ID);
    public static final Charset WORKING_CHARSET = StandardCharsets.UTF_8;

    private static final Logger logger = Logger.getLogger(TrecLiveQaDemoServer.class.getName());

    // private ByteBuffer incomingAnswer; // i don't think it's used here. 
    private int bufferSize = 1024;

    //let's try and create a separate instance of the AnswerServer for every question. 
    // private AnswerServer myAnswerServer;

    //AnswersServer - connects to Python code on port 11001, sends it incoming questions and 
    // awaits for answers

    public TrecLiveQaDemoServer(String hostname, int port) {
        super(hostname, port);
        // setupAnswerServer();
    }

    public TrecLiveQaDemoServer(int port) {
        super(port);
        // setupAnswerServer();
    }

    public void setupAnswerServer() {
        System.out.println("Setting up answer server");
        // incomingAnswer = ByteBuffer.allocate(bufferSize);
        // myAnswerServer = new AnswerServer();
    }

    @Override
    public Response serve(IHTTPSession session) {
        // extract get time from system
        final long getTime = System.currentTimeMillis();
        // logger.info("Got request at " + getTime);
        System.out.println("Got request");

        // read question data
        Map<String, String> files = new HashMap<>();
        Method method = session.getMethod();
        System.out.println("***Session:***");
        System.out.println(session.toString());
        if (Method.POST.equals(method)) {
            try {
                session.parseBody(files);
            } catch (IOException ioe) {
                return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
                        "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            } catch (ResponseException re) {
                return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
            }
        }

        Map<String, String> params = session.getParms();
        String qid = params.get(QUESTION_ID_PARAMETER_NAME);
        String title = params.get(QUESTION_TITLE_PARAMETER_NAME);
        String body = params.get(QUESTION_BODY_PARAMETER_NAME);
        String category = params.get(QUESTION_CATEGORY_PARAMETER_NAME);
        String receivedQuestion = String.format("\nQID: %s\nTITLE: %s\nBODY: %s\nCATEGORY: %s\n", qid, title, body,
                category);

        if (category != null) {
            logger.info(receivedQuestion);
        } else {
            System.out.println("This time the category is null");
        }

        // "get answer"
        // we need to return Response object here. I don't want to go into detail of AnswerAndResources at the moment. 
        // Given a question string receivedQuestion, we will construct a String resp using methods of our class AnswerServer.

        AnswerAndResources answerAndResources = null;
        try {
            // this is where we return our answer
            answerAndResources = getAnswerAndResources(qid, title, body, category);
        } catch (Exception e) {
            logger.warning("Failed to retrieve answer and resources");
            e.printStackTrace();
            return null;
        }

        // heer we wrap our answer in an appropriate format
        // initialize response document

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
        try {
            docBuilder = docFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            logger.warning("Could not build XML document");
            e.printStackTrace();
            return null;
        }

        Document doc = docBuilder.newDocument();
        Element rootElement = doc.createElement(ANSWER_ROOT_ELEMENT_NAME);
        doc.appendChild(rootElement);
        Element answerElement = doc.createElement(ANSWER_BASE_ELEMENT_NAME);
        rootElement.appendChild(answerElement);

        // populate fields
        if (answerAndResources != null) {
            answerElement.setAttribute(ANSWER_ANSWERED_YES_NO_ATTRIBUTE_NAME, YES);
            XmlUtils.addElementWithText(doc, answerElement, ANSWER_CONTENT_ELEMENT_NAME,
                    answerAndResources.answer());
            XmlUtils.addElementWithText(doc, answerElement, ANSWER_RESOURCES_ELEMENT_NAME,
                    answerAndResources.resources());
            logger.info(
                    "Response: " + answerAndResources.answer() + "; Resources: " + answerAndResources.resources());
            // System.out.println("Response: " + answerAndResources.answer() + "; Resources: " + answerAndResources.resources());
        } else {
            answerElement.setAttribute(ANSWER_ANSWERED_YES_NO_ATTRIBUTE_NAME, NO);
            XmlUtils.addElementWithText(doc, answerElement, ANSWER_WHY_NOT_ANSWERED_ELEMENT_NAME, EXCUSE);
            logger.info("No answer given: " + EXCUSE);
            // System.out.println("No answer given: " + EXCUSE);
        }

        final long timeElapsed = System.currentTimeMillis() - getTime;
        answerElement.setAttribute(ANSWER_PARTICIPANT_ID_ATTRIBUTE_NAME, participantId());
        answerElement.setAttribute(ANSWER_REPORTED_TIME_MILLISECONDS_ATTRIBUTE_NAME, Long.toString(timeElapsed));
        answerElement.setAttribute(QUESTION_ID_PARAMETER_NAME, qid);
        logger.info("Internal time logged: " + timeElapsed);
        // System.out.println("Internal time logged: " + timeElapsed);

        String resp = XmlUtils.writeDocumentToString(doc);
        /*End of process AnswerAndResources*/

        // we're creating a new instance of AnswerServer for every incoming question. 
        // when we're creating a new server we need to connect it to the Python server and that's it. 
        // also make sure that the Python server is able to accept multiple connections at a time. it should be i think.
        // move this part to getAnswersAndResources

        // AnswerServer myInstanceAnswerServer = new AnswerServer();
        // String resp = myInstanceAnswerServer.AskQuestion(receivedQuestion);

        // 
        // String resp = "our response";
        System.out.println("Got back to serve(). Response is ");
        // System.out.println(resp);

        return new Response(resp);
    }

    protected String participantId() {
        return PARTICIPANT_ID;
    }

    /**
     * Server's algorithmic payload.
     * 
     * @param qid unique question id
     * @param title question title (roughly 10 words)
     * @param body question body (could be empty, could be lengthy)
     * @param category (verbal description)
     * @return server's answer and a list of resources
     * @throws InterruptedException
     */
    protected AnswerAndResources getAnswerAndResources(String qid, String title, String body, String category)
            throws InterruptedException {

        // String receivedQuestion = title + " " + body;

        AnswerServer myInstanceAnswerServer = new AnswerServer();
        // String resp = myInstanceAnswerServer.AskQuestion(receivedQuestion);
        String resp = myInstanceAnswerServer.AskQuestion(qid, title, body, category);

        String answer = "";
        String resources = "";
        try {
            JSONObject jresp = new JSONObject(resp);

            answer = jresp.getString("answer");
            resources = jresp.getString("source");
        } catch (JSONException e) {
            System.out.println(e);
            resp = NO_ANSWER;
            return null;
        }

        if (resp.equals(NO_ANSWER)) {
            return null;
        }

        // TODO: split resp into answer and resources

        // String answer = resp;
        // String resources = "Resource1; Resourcse2"; 
        return new AnswerAndResources(answer, resources);
    }

    protected static class AnswerAndResources {

        private String answer;
        private String resources;

        public AnswerAndResources(String iAnswer, String iResources) {
            answer = iAnswer;
            resources = iResources;
        }

        public String answer() {
            return answer;
        }

        public String resources() {
            return resources;
        }

    }

    class AnswerServer {
        public static final int PORT_NUMBER = 11001;
        // public AsynchronousServerSocketChannel channel;
        public AsynchronousSocketChannel asyncSocketChannel;
        public ByteBuffer incomingAnswer;
        public Future<Integer> future_read;
        public String response = "default response";

        public AnswerServer() {
            try {
                // TODO: process exception here. If Python server is not running at the moment. ConnectionRefused occurs. 
                this.incomingAnswer = ByteBuffer.allocate(2024);
                this.asyncSocketChannel = AsynchronousSocketChannel.open();
                SocketAddress serverAddr = new InetSocketAddress("localhost", this.PORT_NUMBER);
                Future<Void> result = asyncSocketChannel.connect(serverAddr);
                result.get();
                System.out.println("Answer Server connected to Python Server.");
            } catch (Exception e) {
                System.out.println(e);
            }

            // try {
            //     this.incomingAnswer = ByteBuffer.allocate(2024);
            //     this.channel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(this.PORT_NUMBER));
            //     this.channel.accept(null, new CompletionHandler<AsynchronousSocketChannel,Void>() {
            //     @Override
            //         public void completed(AsynchronousSocketChannel ch, Void att) {
            //             System.out.println("Yay! Established connection successfully.");
            //             asyncSocketChannel = ch;
            //             // HandleUserInput(ch);
            //             }
            //     @Override
            //         public void failed(Throwable exc, Void att) {
            //             System.out.println("failed to connect");
            //         }
            //     });
            // }
            // catch (IOException e ){
            //     System.out.println("exc in establishing a connection");
            // }
        }

        public String readMessage() {
            try {

                long timeout = 60;
                Integer future_read_code = future_read.get(timeout, TimeUnit.SECONDS);
                if (future_read.isDone()) {

                    //only copying the bytes that have just arrived. Offset = 0, length = buffer.position.
                    //creating a new buffer to only contain the newly arrived information. 

                    //java's garbage collector will take care of the memory after we leave this function. 
                    //So there shouln't be any memory leaks. 

                    System.out.println("Incoming Answer position is " + incomingAnswer.position());

                    //returns empty
                    // byte[] recent_response = new byte[incomingAnswer.position()];
                    // incomingAnswer.get(recent_response);

                    // returns empty 
                    // incomingAnswer.get(recent_response, 0, incomingAnswer.position()); 

                    byte[] full_buffer = incomingAnswer.array();
                    response = new String(Arrays.copyOfRange(full_buffer, 0, incomingAnswer.position()));
                    //works fine, but returns the entire contents of the buffer, which sometimes contains previous data
                    // response = new String(incomingAnswer.array());
                    // response = new String(recent_response);

                    // System.out.println("Reponse from Python server has come and it is: ");
                    // System.out.println(response);
                    this.asyncSocketChannel = this.asyncSocketChannel.shutdownInput();
                } else {
                    System.out.println("Future is not done.");
                }
            } catch (TimeoutException e) {
                System.out.println("Timeout exception ");
                response = NO_ANSWER;
            } catch (Exception e) {
                System.out.println("An exception in asking question occured!!!");
                System.out.println("If connection was reset by peer, then we must have run out of time");
                e.printStackTrace(System.out);
                response = NO_ANSWER;
                // System.out.println("exc in getting an answer");
            }

            return response;
        }

        public String AskQuestion(String qid, String title, String body, String category) {
            // ByteBuffer message = ByteBuffer.wrap(question.getBytes());
            JSONObject jobj = new JSONObject();
            jobj.put("qid", qid);
            jobj.put("title", title);
            jobj.put("body", body);
            jobj.put("category", category);

            String question = jobj.toString();

            System.out.println("Asking a question");
            ByteBuffer message = ByteBuffer.wrap(question.getBytes());
            // System.out.println("Yahho is asking: " + question);

            //set response to an empty answer in case we don't have enough time to find an actual response. 
            response = "We didn't have enough time to come up with an aswer.";

            //sending message
            SentQuestionCompletionHandler handler = new SentQuestionCompletionHandler(this.asyncSocketChannel);
            this.asyncSocketChannel.write(message, null, handler);

            //waiting for a response to come in
            System.out.println("Waiting for respone to come in");
            while (!handler.gotResponse) {
                // boolean voidFiller = handler.gotResponse;
                // System.out.println("Handler.got response is " + handler.gotResponse);
                // System.out.println("Some stuuuuff");

                //if the loop has println in it, everything works fine. Anything else instead of println gets stuck 
                // at this point. 
                //maybe if I try to put an operation that takes a long time it would help somehow. 
                //Will try sleep for 1 sec
                // Sleep operation seemed to help. No idea why though. 
                // 1 second maybe too long. I'll try to turn it down bit by bit and see what happens. 
                // down to 500 ms sleep - works fine. 
                // down to 250 ms sleep - also good. 
                // down to 100 ms sleep - ok. Leave it here. 

                try {
                    Thread.sleep(100);
                } catch (InterruptedException ie) {
                    System.out.println("Exception in while the thread is sleeping. Deal with it later.");
                }
            }

            //now the response variable has a valid value in it 
            System.out.println("Returning response from Ask Question");

            try {
                System.out.println("Closing asyncSocketChannel");
                // safeclose here?
                this.asyncSocketChannel.close();
            } catch (IOException e) {
                System.out.println("Could not close the channel.");
                System.out.println(e);
            }
            return response;
        }

        class SentQuestionCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {

            AsynchronousSocketChannel channel;
            boolean gotResponse = false;

            public SentQuestionCompletionHandler(AsynchronousSocketChannel ch) {

                this.channel = ch;
            }

            @Override
            public void completed(Integer result, ByteBuffer message) {
                System.out.println("Successfully sent message. Awaiting for response.");

                // this resets the position, but all the old data is still in the buffer. 
                // solution1: clear all the old data in it first
                // solution2: keep track of how long the response it and only use those bytes. Then the old data won't matter.
                // Going with solution2. It is done in readMessage()

                incomingAnswer.clear();
                // future_read = null;

                future_read = this.channel.read(incomingAnswer);
                readMessage();

                this.gotResponse = true;
                System.out.println("gotResponse is true");
                // this.channel.read(incomingAnswer, 10, TimeUnit.SECONDS, null, new IncomingAnswerCompletionHandler(this.channel, incomingAnswer));
                // }
                // catch (InterruptedByTimeoutException e)
                // {
                //     System.out.println("Timeout.");
                // }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer message) {
                System.out.println("Failed to send message");
                System.out.println(message);
            }
        }
    }

    // ---------------------------------------------

    public static void main(String[] args) throws IOException {
        TrecLiveQaDemoServer server = new TrecLiveQaDemoServer(
                args.length == 0 ? DEFAULT_PORT : Integer.parseInt(args[0]));

        Handler fh = new FileHandler(LOG_FILENAME);
        logger.addHandler(fh);
        logger.finest("Test message");

        server.start();
        System.in.read();
        server.stop();
    }

}