Java tutorial
/* * Copyright (c) 2015. * * This file is part of VaSOLSim. * * VaSOLSim is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VaSOLSim is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VaSOLSim. If not, see <http://www.gnu.org/licenses/>. */ package main.java.vasolsim.common.file; import javax.annotation.Nonnull; import main.java.vasolsim.common.GenericUtils; import main.java.vasolsim.common.VaSolSimException; import main.java.vasolsim.common.notification.PopupManager; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.crypto.Cipher; import javax.imageio.ImageIO; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_COULD_NOT_CREATE_DIRS; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_COULD_NOT_CREATE_FILE; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_CREATE_FILE_EXCEPTION; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_FILE_ALREADY_EXISTS; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_GENERIC_CRYPTO; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_INTERNAL_TRANSFORMER_CONFIGURATION; import static main.java.vasolsim.common.GenericUtils.ERROR_MESSAGE_INTERNAL_XML_PARSER_INITIALIZATION_EXCEPTION; import static main.java.vasolsim.common.GenericUtils.convertBytesToHexString; import static main.java.vasolsim.common.GenericUtils.errorsToOutput; /** * @author willstuckey * @date 10/31/14 <p></p> */ public class ExamBuilder { ////////////////////////////// // XML STRUCTURE CONSTANTS // @SuppressWarnings("unused") private static final boolean __BEGIN_XML_STRUCTURE_CONSTANTS = false; ////////////////////////////// //system keys public static final String INDENTATION_KEY = "{http://xml.apache.org/xslt}indent-amount"; //root depth public static final String XML_ROOT_ELEMENT_NAME = "vssroot"; //root+ depth public static final String XML_INFO_ELEMENT_NAME = "info"; //root++ depth (information+) public static final String XML_TEST_NAME_ELEMENT_NAME = "testName"; public static final String XML_AUTHOR_NAME_ELEMENT_NAME = "author"; public static final String XML_SCHOOL_NAME_ELEMENT_NAME = "school"; public static final String XML_PERIOD_NAME_ELEMENT_NAME = "class"; public static final String XML_DATE_ELEMENT_NAME = "date"; //root+ depth public static final String XML_SECURITY_ELEMENT_NAME = "sec"; //root++ depth (sec+) public static final String XML_ENCRYPTED_VALIDATION_HASH_ELEMENT_NAME = "encValHash"; public static final String XML_PARAMETRIC_INITIALIZATION_VECTOR_ELEMENT_NAME = "paramIV"; public static final String XML_IS_REPORTING_STATISTICS_ELEMENT_NAME = "statsReporting"; public static final String XML_IS_REPORTING_STATISTICS_STANDALONE_ELEMENT_NAME = "statsStandalone"; public static final String XML_IS_ENCRYPTING_STATISTICS_ELEMENT_NAME = "statsEnc"; public static final String XML_STATISTICS_DESTINATION_EMAIL_ADDRESS_ELEMENT_NAME = "statsDestEmail"; public static final String XML_STATISTICS_SENDER_EMAIL_ADDRESS_ELEMENT_NAME = "statsSender"; public static final String XML_STATISTICS_SENDER_EMAIL_PASSWORD_ELEMENT_NAME = "statsSenderPw"; public static final String XML_STATISTICS_SENDER_SMTP_ADDRESS_ELEMENT_NAME = "statsSenderSMTPAddr"; public static final String XML_STATISTICS_SENDER_SMTP_PORT_ELEMENT_NAME = "statsSenderSMTPPort"; //root+ depth public static final String XML_QUESTION_SET_ELEMENT_NAME = "questionSet"; //root++ depth (questionSet+) public static final String XML_QUESTION_SET_ID_ELEMENT_NAME = "setID"; public static final String XML_QUESTION_SET_NAME_ELEMENT_NAME = "setName"; public static final String XML_QUESTION_SET_RESOURCE_TYPE_ELEMENT_NAME = "rscType"; public static final String XML_QUESTION_SET_RESOURCE_DATA_ELEMENT_NAME = "rscData"; public static final String XML_QUESTION_ELEMENT_NAME = "question"; //root+++ depth (questionSet++, questionGrouping+) public static final String XML_QUESTION_ID_ELEMENT_NAME = "questionID"; public static final String XML_QUESTION_NAME_ELEMENT_NAME = "questionName"; public static final String XML_QUESTION_TEXT_ELEMENT_NAME = "questionText"; public static final String XML_QUESTION_SCRAMBLE_ANSWERS_ELEMENT_NAME = "scramAns"; public static final String XML_QUESTION_REATIAN_ANSWER_ORDER_ELEMENT_NAME = "retOrder"; public static final String XML_QUESTION_ENCRYPTED_ANSWER_HASH = "encAnsHash"; public static final String XML_ANSWER_CHOICE_ELEMENT_NAME = "answerChoice"; //root++++ depth (questionSet+++, questionGrouping++, answer+) public static final String XML_ANSWER_CHOICE_ID_ELEMENT_NAME = "answerID"; public static final String XML_ANSWER_CHOICE_VISIBLE_ID_ELEMENT_NAME = "answerVisibleID"; public static final String XML_ANSWER_TEXT_ELEMENT_NAME = "answer"; public static Logger logger = Logger.getLogger(ExamBuilder.class.getName()); static { logger.setLevel(Level.TRACE); } /** * Writes an exam to an XML file * * @param exam the exam to be written * @param examFile the target file * @param password the passphrase locking the restricted content * * @return if the write was successful * * @throws VaSolSimException */ public static boolean writeExam(@Nonnull Exam exam, @Nonnull File examFile, @Nonnull String password) throws VaSolSimException { return writeExam(exam, examFile, password, false); } /** * Writes an exam to an XML file * * @param exam the exam to be written * @param examFile the target file * @param password the passphrase locking the restricted content * @param overwrite if an existing file can be overwritten * * @return if the write was successful * * @throws VaSolSimException */ public static boolean writeExam(@Nonnull Exam exam, @Nonnull File examFile, @Nonnull String password, boolean overwrite) throws VaSolSimException { logger.info("beginning exam export -> " + exam.getTestName()); logger.debug("checking export destination..."); /* * check the file creation status and handle it */ //if it exists if (examFile.isFile()) { logger.trace("exam file exists, checking overwrite..."); //can't overwrite if (!overwrite) { logger.error("file already present and cannot overwrite"); throw new VaSolSimException(ERROR_MESSAGE_FILE_ALREADY_EXISTS); } //can overwrite, clear the existing file else { logger.trace("overwriting..."); PrintWriter printWriter; try { printWriter = new PrintWriter(examFile); } catch (FileNotFoundException e) { logger.error("internal file presence check failed", e); throw new VaSolSimException(ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK); } printWriter.print(""); printWriter.close(); } } //no file, create one else { logger.trace("exam file does not exist, creating..."); if (!examFile.getParentFile().isDirectory() && !examFile.getParentFile().mkdirs()) { logger.error("could not create empty directories for export"); throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_DIRS); } try { logger.trace("creating files..."); if (!examFile.createNewFile()) { logger.error("could not create empty file for export"); throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_FILE); } } catch (IOException e) { logger.error("io error on empty file creation", e); throw new VaSolSimException(ERROR_MESSAGE_CREATE_FILE_EXCEPTION); } } logger.debug("initializing weak cryptography scheme..."); /* * initialize the cryptography system */ String encryptedHash; Cipher encryptionCipher; try { logger.trace("hashing password into key..."); //hash the password byte[] hash; MessageDigest msgDigest = MessageDigest.getInstance("SHA-512"); msgDigest.update(password.getBytes()); hash = GenericUtils.validate512HashTo128Hash(msgDigest.digest()); logger.trace("initializing cipher"); encryptionCipher = GenericUtils.initCrypto(hash, Cipher.ENCRYPT_MODE); encryptedHash = GenericUtils .convertBytesToHexString(GenericUtils.applyCryptographicCipher(hash, encryptionCipher)); } catch (NoSuchAlgorithmException e) { logger.error("FAILED. could not initialize crypto", e); throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD ALGORITHM\n" + e.toString() + "\n" + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e); } logger.debug("initializing the document builder..."); /* * initialize the document */ Document examDoc; Transformer examTransformer; try { logger.trace("create document builder factory instance -> create new doc"); examDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); logger.trace("set document properties"); examTransformer = TransformerFactory.newInstance().newTransformer(); examTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); examTransformer.setOutputProperty(OutputKeys.METHOD, "xml"); examTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); examTransformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "roles.dtd"); examTransformer.setOutputProperty(INDENTATION_KEY, "4"); } catch (ParserConfigurationException e) { logger.error("parser was not configured correctly", e); throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_XML_PARSER_INITIALIZATION_EXCEPTION, e); } catch (TransformerConfigurationException e) { logger.error("transformer was not configured properly"); throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_TRANSFORMER_CONFIGURATION, e); } logger.debug("building document..."); /* * build exam info */ logger.trace("attaching root..."); Element root = examDoc.createElement(XML_ROOT_ELEMENT_NAME); examDoc.appendChild(root); logger.trace("attaching info..."); Element info = examDoc.createElement(XML_INFO_ELEMENT_NAME); root.appendChild(info); //exam info logger.trace("attaching exam info..."); GenericUtils.appendSubNode(XML_TEST_NAME_ELEMENT_NAME, exam.getTestName(), info, examDoc); GenericUtils.appendSubNode(XML_AUTHOR_NAME_ELEMENT_NAME, exam.getAuthorName(), info, examDoc); GenericUtils.appendSubNode(XML_SCHOOL_NAME_ELEMENT_NAME, exam.getSchoolName(), info, examDoc); GenericUtils.appendSubNode(XML_PERIOD_NAME_ELEMENT_NAME, exam.getPeriodName(), info, examDoc); GenericUtils.appendSubNode(XML_DATE_ELEMENT_NAME, exam.getDate(), info, examDoc); //start security xml section logger.trace("attaching security..."); Element security = examDoc.createElement(XML_SECURITY_ELEMENT_NAME); root.appendChild(security); GenericUtils.appendSubNode(XML_ENCRYPTED_VALIDATION_HASH_ELEMENT_NAME, encryptedHash, security, examDoc); GenericUtils.appendSubNode(XML_PARAMETRIC_INITIALIZATION_VECTOR_ELEMENT_NAME, GenericUtils.convertBytesToHexString(encryptionCipher.getIV()), security, examDoc); GenericUtils.appendSubNode(XML_IS_REPORTING_STATISTICS_ELEMENT_NAME, Boolean.toString(exam.isReportingStats()), security, examDoc); GenericUtils.appendSubNode(XML_IS_REPORTING_STATISTICS_STANDALONE_ELEMENT_NAME, Boolean.toString(exam.isReportingStatsStandalone()), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_DESTINATION_EMAIL_ADDRESS_ELEMENT_NAME, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( exam.getStatsDestinationEmail() == null ? GenericUtils.NO_EMAIL.getBytes() : exam.getStatsDestinationEmail().getBytes(), encryptionCipher)), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_EMAIL_ADDRESS_ELEMENT_NAME, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( exam.getStatsSenderEmail() == null ? GenericUtils.NO_EMAIL.getBytes() : exam.getStatsSenderEmail().getBytes(), encryptionCipher)), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_EMAIL_PASSWORD_ELEMENT_NAME, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( exam.getStatsSenderPassword() == null ? GenericUtils.NO_DATA.getBytes() : exam.getStatsSenderPassword().getBytes(), encryptionCipher)), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_SMTP_ADDRESS_ELEMENT_NAME, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( exam.getStatsSenderSMTPAddress() == null ? GenericUtils.NO_SMTP.getBytes() : exam.getStatsSenderSMTPAddress().getBytes(), encryptionCipher)), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_SMTP_PORT_ELEMENT_NAME, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( Integer.toString(exam.getStatsSenderSMTPPort()).getBytes(), encryptionCipher)), security, examDoc); logger.debug("checking exam content integrity..."); ArrayList<QuestionSet> questionSets = exam.getQuestionSets(); if (GenericUtils.checkExamIntegrity(exam).size() == 0) { logger.debug("exporting exam content..."); for (int setsIndex = 0; setsIndex < questionSets.size(); setsIndex++) { QuestionSet qSet = questionSets.get(setsIndex); logger.trace("exporting question set -> " + qSet.getName()); Element qSetElement = examDoc.createElement(XML_QUESTION_SET_ELEMENT_NAME); root.appendChild(qSetElement); GenericUtils.appendSubNode(XML_QUESTION_SET_ID_ELEMENT_NAME, Integer.toString(setsIndex + 1), qSetElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_SET_NAME_ELEMENT_NAME, (qSet.getName().equals("")) ? "Question Set " + (setsIndex + 1) : qSet.getName(), qSetElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_SET_RESOURCE_TYPE_ELEMENT_NAME, qSet.getResourceType().toString(), qSetElement, examDoc); if (qSet.getResourceType() != GenericUtils.ResourceType.NONE && qSet.getResources() != null) { logger.debug("exporting question set resources..."); for (BufferedImage img : qSet.getResources()) { if (img != null) { try { logger.trace("writing image..."); ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(img, "png", out); out.flush(); GenericUtils.appendCDATASubNode(XML_QUESTION_SET_RESOURCE_DATA_ELEMENT_NAME, new String(Base64.encodeBase64(out.toByteArray())), qSetElement, examDoc); } catch (IOException e) { throw new VaSolSimException( "Error: cannot write images to byte array for transport"); } } } } //TODO export problem in this subroutine for (int setIndex = 0; setIndex < qSet.getQuestions().size(); setIndex++) { Question question = qSet.getQuestions().get(setIndex); logger.trace("exporting question -> " + question.getName()); Element qElement = examDoc.createElement(XML_QUESTION_ELEMENT_NAME); qSetElement.appendChild(qElement); logger.trace("question id -> " + setIndex); GenericUtils.appendSubNode(XML_QUESTION_ID_ELEMENT_NAME, Integer.toString(setIndex + 1), qElement, examDoc); logger.trace("question name -> " + question.getName()); GenericUtils.appendSubNode(XML_QUESTION_NAME_ELEMENT_NAME, (question.getName().equals("")) ? "Question " + (setIndex + 1) : question.getName(), qElement, examDoc); logger.trace("question test -> " + question.getQuestion()); GenericUtils.appendSubNode(XML_QUESTION_TEXT_ELEMENT_NAME, question.getQuestion(), qElement, examDoc); logger.trace("question answer scramble -> " + Boolean.toString(question.getScrambleAnswers())); GenericUtils.appendSubNode(XML_QUESTION_SCRAMBLE_ANSWERS_ELEMENT_NAME, Boolean.toString(question.getScrambleAnswers()), qElement, examDoc); logger.trace("question answer order matters -> " + Boolean.toString(question.getAnswerOrderMatters())); GenericUtils.appendSubNode(XML_QUESTION_REATIAN_ANSWER_ORDER_ELEMENT_NAME, Boolean.toString(question.getAnswerOrderMatters()), qElement, examDoc); logger.debug("exporting correct answer choices..."); for (AnswerChoice answer : question.getCorrectAnswerChoices()) { logger.trace("exporting correct answer choice(s) -> " + answer.getAnswerText()); GenericUtils .appendSubNode(XML_QUESTION_ENCRYPTED_ANSWER_HASH, GenericUtils.convertBytesToHexString(GenericUtils.applyCryptographicCipher( answer.getAnswerText().getBytes(), encryptionCipher)), qElement, examDoc); } logger.debug("exporting answer choices..."); for (int questionIndex = 0; questionIndex < question.getAnswerChoices() .size(); questionIndex++) { if (question.getAnswerChoices().get(questionIndex).isActive()) { AnswerChoice ac = question.getAnswerChoices().get(questionIndex); logger.trace("exporting answer choice -> " + ac.getAnswerText()); Element acElement = examDoc.createElement(XML_ANSWER_CHOICE_ELEMENT_NAME); qElement.appendChild(acElement); logger.trace("answer choice id -> " + questionIndex); GenericUtils.appendSubNode(XML_ANSWER_CHOICE_ID_ELEMENT_NAME, Integer.toString(questionIndex + 1), acElement, examDoc); logger.trace("answer choice visible id -> " + ac.getVisibleChoiceID()); GenericUtils.appendSubNode(XML_ANSWER_CHOICE_VISIBLE_ID_ELEMENT_NAME, ac.getVisibleChoiceID(), acElement, examDoc); logger.trace("answer text -> " + ac.getAnswerText()); GenericUtils.appendSubNode(XML_ANSWER_TEXT_ELEMENT_NAME, ac.getAnswerText(), acElement, examDoc); } } } } } else { logger.error("integrity check failed"); PopupManager.showMessage(errorsToOutput(GenericUtils.checkExamIntegrity(exam))); return false; } logger.debug("transforming exam..."); try { examTransformer.transform(new DOMSource(examDoc), new StreamResult(examFile)); } catch (TransformerException e) { logger.error("exam export failed (transformer error)", e); return false; } logger.debug("transformation done"); logger.info("exam export successful"); return true; } /** * reads an exam from a file * * @param examFile the file to read * @param password the passphrase that will unlock the file * * @return the initialized exam * * @throws VaSolSimException */ public static Exam readExam(@Nonnull File examFile, @Nonnull String password) throws VaSolSimException { return null; } /** * writes an editable, unlocked exam to a file * * @param exam the exam to be written * @param examFile the target file * * @return if the write was successful * * @throws VaSolSimException */ public static boolean writeRaw(@Nonnull Exam exam, @Nonnull File examFile) throws VaSolSimException { return writeRaw(exam, examFile, true); } /** * writes an editable, unlocked exam to a file * * @param exam the exam to be written * @param examFile the target file * @param overwrite if an existing file can be overwritten * * @return if the file write was successful * * @throws VaSolSimException */ public static boolean writeRaw(@Nonnull Exam exam, @Nonnull File examFile, boolean overwrite) throws VaSolSimException { /* * check the file creation status and handle it */ //if it exists if (examFile.isFile()) { //can't overwrite if (!overwrite) { throw new VaSolSimException(ERROR_MESSAGE_FILE_ALREADY_EXISTS); } //can overwrite, clear the existing file else { PrintWriter printWriter; try { printWriter = new PrintWriter(examFile); } catch (FileNotFoundException e) { throw new VaSolSimException(ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK); } printWriter.print(""); printWriter.close(); } } //no file, create one else { if (!examFile.getParentFile().isDirectory() && !examFile.getParentFile().mkdirs()) { throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_DIRS); } try { if (!examFile.createNewFile()) { throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_FILE); } } catch (IOException e) { throw new VaSolSimException(ERROR_MESSAGE_CREATE_FILE_EXCEPTION); } } /* * initialize the document */ Document examDoc; Transformer examTransformer; try { examDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); examTransformer = TransformerFactory.newInstance().newTransformer(); examTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); examTransformer.setOutputProperty(OutputKeys.METHOD, "xml"); examTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); examTransformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "roles.dtd"); examTransformer.setOutputProperty(INDENTATION_KEY, "4"); } catch (ParserConfigurationException e) { throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_XML_PARSER_INITIALIZATION_EXCEPTION, e); } catch (TransformerConfigurationException e) { throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_TRANSFORMER_CONFIGURATION, e); } /* * build exam info */ Element root = examDoc.createElement(XML_ROOT_ELEMENT_NAME); examDoc.appendChild(root); Element info = examDoc.createElement(XML_INFO_ELEMENT_NAME); root.appendChild(info); //exam info GenericUtils.appendSubNode(XML_TEST_NAME_ELEMENT_NAME, exam.getTestName(), info, examDoc); GenericUtils.appendSubNode(XML_AUTHOR_NAME_ELEMENT_NAME, exam.getAuthorName(), info, examDoc); GenericUtils.appendSubNode(XML_SCHOOL_NAME_ELEMENT_NAME, exam.getSchoolName(), info, examDoc); GenericUtils.appendSubNode(XML_PERIOD_NAME_ELEMENT_NAME, exam.getPeriodName(), info, examDoc); //start security xml section Element security = examDoc.createElement(XML_SECURITY_ELEMENT_NAME); root.appendChild(security); GenericUtils.appendSubNode(XML_IS_REPORTING_STATISTICS_ELEMENT_NAME, Boolean.toString(exam.isReportingStats()), security, examDoc); GenericUtils.appendSubNode(XML_IS_REPORTING_STATISTICS_STANDALONE_ELEMENT_NAME, Boolean.toString(exam.isReportingStatsStandalone()), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_DESTINATION_EMAIL_ADDRESS_ELEMENT_NAME, exam.getStatsDestinationEmail(), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_EMAIL_ADDRESS_ELEMENT_NAME, exam.getStatsSenderEmail(), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_SMTP_ADDRESS_ELEMENT_NAME, exam.getStatsSenderSMTPAddress(), security, examDoc); GenericUtils.appendSubNode(XML_STATISTICS_SENDER_SMTP_PORT_ELEMENT_NAME, Integer.toString(exam.getStatsSenderSMTPPort()), security, examDoc); ArrayList<QuestionSet> questionSets = exam.getQuestionSets(); if (GenericUtils.verifyQuestionSetsIntegrity(questionSets)) { for (int setsIndex = 0; setsIndex < questionSets.size(); setsIndex++) { QuestionSet qSet = questionSets.get(setsIndex); Element qSetElement = examDoc.createElement(XML_QUESTION_SET_ELEMENT_NAME); root.appendChild(qSetElement); GenericUtils.appendSubNode(XML_QUESTION_SET_ID_ELEMENT_NAME, Integer.toString(setsIndex + 1), qSetElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_SET_NAME_ELEMENT_NAME, (qSet.getName() == null || qSet.getName().equals("")) ? "Question Set " + (setsIndex + 1) : qSet.getName(), qSetElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_SET_RESOURCE_TYPE_ELEMENT_NAME, qSet.getResourceType().toString(), qSetElement, examDoc); if (qSet.getResources() != null) { for (BufferedImage img : qSet.getResources()) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(img, "png", out); out.flush(); GenericUtils.appendSubNode(XML_QUESTION_SET_RESOURCE_DATA_ELEMENT_NAME, convertBytesToHexString(out.toByteArray()), qSetElement, examDoc); } catch (IOException e) { throw new VaSolSimException("Error: cannot write images to byte array for transport"); } } } for (int setIndex = 0; setIndex < qSet.getQuestions().size(); setIndex++) { Question question = qSet.getQuestions().get(setIndex); Element qElement = examDoc.createElement(XML_QUESTION_ELEMENT_NAME); qSetElement.appendChild(qElement); GenericUtils.appendSubNode(XML_QUESTION_ID_ELEMENT_NAME, Integer.toString(setIndex + 1), qElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_NAME_ELEMENT_NAME, (question.getName() == null || question.getName().equals("")) ? "Question " + (setIndex + 1) : question.getName(), qElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_TEXT_ELEMENT_NAME, question.getQuestion(), qElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_SCRAMBLE_ANSWERS_ELEMENT_NAME, Boolean.toString(question.getScrambleAnswers()), qElement, examDoc); GenericUtils.appendSubNode(XML_QUESTION_REATIAN_ANSWER_ORDER_ELEMENT_NAME, Boolean.toString(question.getAnswerOrderMatters()), qElement, examDoc); for (AnswerChoice answer : question.getCorrectAnswerChoices()) { GenericUtils.appendSubNode(XML_QUESTION_ENCRYPTED_ANSWER_HASH, answer.getAnswerText(), qElement, examDoc); } for (int questionIndex = 0; questionIndex < question.getAnswerChoices() .size(); questionIndex++) { AnswerChoice ac = question.getAnswerChoices().get(questionIndex); Element acElement = examDoc.createElement(XML_ANSWER_CHOICE_ELEMENT_NAME); qElement.appendChild(acElement); GenericUtils.appendSubNode(XML_ANSWER_CHOICE_ID_ELEMENT_NAME, Integer.toString(questionIndex + 1), acElement, examDoc); GenericUtils.appendSubNode(XML_ANSWER_CHOICE_VISIBLE_ID_ELEMENT_NAME, ac.getVisibleChoiceID(), acElement, examDoc); GenericUtils.appendSubNode(XML_ANSWER_TEXT_ELEMENT_NAME, ac.getAnswerText(), acElement, examDoc); } } } } return true; } /** * reads an editable exam from a file * * @param examFile the file to read * * @return the initialized exam */ public static Exam readRaw(@Nonnull File examFile) { return null; } }