Java tutorial
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); } } }