Question.java Source code

Java tutorial

Introduction

Here is the source code for Question.java

Source

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;

import com.amazonaws.mturk.addon.HITQuestion;

/**
 * This class encapsulates a question which is placed into a Hit.
 * 
 * @author Bruce Giese
 * @since Sept 26, 2014
 * 
 * This software is covered by Apache v2.0 license http://www.apache.org/licenses/LICENSE-2.0.html
 * 
 * Github project located at https://github.com/BruceGiese/ever-turking-story
 * Author's website is located at http://software.brucegiese.com/category/ever-turking-story/
 *
 */

public class Question {
    static org.apache.log4j.Logger logger;
    private Document doc;
    private static final String tempDirName = "./temp"; // temp dir for intermediate results
    private static final String tempFilePrefix = "tempQuestion";
    private static final String XMLFileSuffix = "xml";
    private File tempFile;
    private Element root;
    private Namespace ns;

    /**
     * 
     * @param questionTemplateFile      This file contains the static XML code to implement the question.  See Amazon's documentation.
     * @throws StoryException
     */
    public Question(String questionTemplateFile) throws StoryException {
        logger = Logger.getLogger(Log.LOGGING_PARENT + ".Question");

        try {
            logger.debug("Parsing in the question template...");
            doc = new SAXBuilder().build(questionTemplateFile);

            root = doc.getRootElement();

            logger.debug("Setting up a temporary output file...");
            File tempDir = new File(tempDirName);
            tempFile = File.createTempFile(tempFilePrefix, XMLFileSuffix, tempDir);

        } catch (IOException ioe) {
            logger.error("Question constructor, reading " + questionTemplateFile);
            logger.info(ioe);
            throw new StoryException("Question constructor, reading template file" + questionTemplateFile);
        } catch (JDOMException jde) {
            logger.error("Question constructor, SAX parsing " + questionTemplateFile);
            logger.info(jde);
            throw new StoryException("Question constructor, SAX parsing error " + questionTemplateFile);
        } catch (NullPointerException npe) {
            logger.error("Internal screwup creating the temp file in " + tempDirName + "; file name was null?");
            logger.info(npe);
            throw new StoryException("Question constructor, internal error creating temp file in " + tempDirName);
        }
    }

    /**
     * Add the content of the question to the template.  This adds a string to one particular element of the XML
     * template.
     * 
     * @param questionContent      We only allow the question content element of the question to vary dynamically.
     * @throws StoryException
     */
    public void addQuestionContent(String questionContent) throws StoryException {
        // This should REALLY be done using Xpath, but Amazon's mturk doesn't use a prefix to their xmlns definition
        // and XPath maps this into the null namespace.

        logger.debug("getting the XML element for the QuestionContent...");
        Element question = root.getChild("Question", ns);
        if (question == null) {
            throw new StoryException("The XML template file doesn't contain a proper Question element");
        }
        Element qc = question.getChild("QuestionContent", ns);
        if (qc == null) {
            throw new StoryException("The XML template file doesn't contain a proper QuestionContent element");
        }
        Element txt = qc.getChild("Text", ns);
        if (txt == null) {
            throw new StoryException("The XML template file's QuestionContent element doesn't have a Text element");
        }

        logger.debug("adding the StoryText to the template XML...");
        txt.addContent(questionContent);
    }

    /**
     * This adds one radio button selection to a radio button based HIT template.  If the questionTemplateFile doesn't
     * contain a StyleSuggestion element containing "radiobutton", then this method won't work.
     * 
     * @param radioButtonText
     * @param radioButtonIdentifier
     * @throws StoryException
     */
    public void addRadioButtonSpecification(String radioButtonText, String radioButtonIdentifier)
            throws StoryException {
        // This should REALLY be done using Xpath, but Amazon's mturk doesn't use a prefix to their xmlns definition
        // and XPath maps this into the null namespace.
        logger.debug("getting the XML element for the Selection...");
        Element question = root.getChild("Question", null);
        if (question == null) {
            throw new StoryException("The XML template file doesn't contain a proper Question element");
        }
        Element answerSpecification = question.getChild("AnswerSpecification", null);
        if (answerSpecification == null) {
            throw new StoryException("The XML template file doesn't contain a proper AnswerSpecification element");
        }
        Element selectionAnswer = answerSpecification.getChild("SelectionAnswer", null);
        if (selectionAnswer == null) {
            throw new StoryException("The XML template file doesn't contain a proper SelectionAnswer element");
        }
        Element selections = selectionAnswer.getChild("Selections", null);
        if (selections == null) {
            throw new StoryException("The XML template file's QuestionContent element doesn't have a Text element");
        }

        // Adding in the namespace URI is the only way to keep JDOM2 from adding a null namespace to the element
        Element selection = new Element("Selection",
                "http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd");
        Element selectionIdentifier = new Element("SelectionIdentifier",
                "http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd");
        Element text = new Element("Text",
                "http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd");
        text.addContent(radioButtonText);
        selectionIdentifier.addContent(radioButtonIdentifier);
        selection.addContent(selectionIdentifier);
        selection.addContent(text);
        selections.addContent(selection);

    }

    /**
     * Get the final question AFTER adding the dynamic part using addQuestionContent().
     * 
     * @return the Amazon HITQuestion object needed to give to Amazon.
     * @throws StoryException
     */
    public HITQuestion getHITQuestion() throws StoryException {
        HITQuestion hitQ = null;
        // Loading the question (QAP) file. HITQuestion is a helper class that
        // contains the QAP of the HIT defined in the external file. This feature 
        // allows you to write the entire QAP externally as a file and be able to 
        // modify it without recompiling your code.
        logger.debug("loading the HIT file...");
        try {
            hitQ = new HITQuestion(tempFile.getPath());
        } catch (Exception e) {
            // For some reason, the IDE thinks getPath() throws an Exception.
            logger.error("tempFile.getPath() threw an exception and it's really not supposed to throw anything.");
            throw new StoryException(
                    "Java File.getPath() threw an exception, probably an internal problem here with file names, protections, etc.");
        }

        return hitQ;
    }

    /**
     * We use a temporary file between creating the question and sending it to Amazon.  This is
     * probably not the cleanest way to do this, but it gives you a record of the HITs.
     * 
     * @throws StoryException
     */
    public void writeOutQuestionFile() throws StoryException {
        try {
            logger.debug("writing out the HIT to the file" + tempFile + " ...");
            XMLOutputter xmlOutput = new XMLOutputter();
            xmlOutput.output(doc, new FileOutputStream(tempFile));
        } catch (IOException ioe) {
            throw new StoryException("IO Exception when adding content to question: " + ioe);
        }
    }

}