package net.sf.egonet.persistence; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.Set; import java.util.ArrayList; import net.sf.egonet.model.Alter; import net.sf.egonet.model.Answer; import net.sf.egonet.model.Entity; import net.sf.egonet.model.Expression; import net.sf.egonet.model.Interview; import net.sf.egonet.model.Question; import net.sf.egonet.model.QuestionOption; import net.sf.egonet.model.Study; import net.sf.egonet.model.Question.QuestionType; import net.sf.egonet.model.AnswerList; import net.sf.egonet.model.AnswerListMgr; import net.sf.egonet.web.panel.NumericLimitsPanel.NumericLimitType; import net.sf.egonet.web.page.CheckIncludeID; import net.sf.functionalj.tuple.Pair; import net.sf.functionalj.tuple.Triple; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import org.hibernate.Session; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import com.google.common.collect.TreeMultiset; public class Archiving { public static String getStudyXML(final Study study) { return new DB.Action<String>() { public String get() { return getStudyXML(session, study,false, (List<CheckIncludeID>)(null)); } }.execute(); } public static String getRespondentDataXML(final Study study, final List<CheckIncludeID> checkIncludeIDList ) { return new DB.Action<String>() { public String get() { return getStudyXML(session, study,true,checkIncludeIDList); } }.execute(); } private static void addText(Element root, String elementName, String elementText) { root.addElement(elementName).addText(replaceProblemCharacters(elementText)); } private static void addAttribute(Element element, String attrKey, Object attrValue) { element.addAttribute(attrKey, replaceProblemCharacters(attrValue)); } private static String replaceProblemCharacters(Object object) { String string = object == null ? "" : object.toString(); // smart double-quotes string = string.replaceAll("[\\u201c\\u201d\\u201e\\u201f\\u2033\\u2036]", "\""); // smart single-quotes and apostrophes string = string.replaceAll("[\\u2018\\u2019\\u201a\\u201b\\u2032\\u2035]", "'"); // unusual dash types string = string.replaceAll("[\\u2012\\u2013\\u2014\\u2015]","-"); return string; } public static String getStudyXML(Session session, Study study, Boolean includeInterviews, List<CheckIncludeID> checkIncludeIDList ) { try { Document document = DocumentHelper.createDocument(); Element studyNode = addStudyNode(document, study); Element questionsNode = studyNode.addElement("questions"); Multiset<QuestionType> questionsOfTypeSoFar = TreeMultiset.create(); for(Question question : Questions.getQuestionsForStudy(session, study.getId(), null)) { addQuestionNode(questionsNode,question, Options.getOptionsForQuestion(session, question.getId()), questionsOfTypeSoFar.count(question.getType())); questionsOfTypeSoFar.add(question.getType()); } Element expressionsNode = studyNode.addElement("expressions"); for(Expression expression : Expressions.forStudy(session, study.getId())) { addExpressionNode(expressionsNode,expression); } Element answerListsNode = studyNode.addElement("answerLists"); AnswerList[] answerListArray = AnswerListMgr.getAnswerLists(study.getId()); for ( AnswerList answerList : answerListArray ) addAnswerListNode(answerListsNode,answerList); if(includeInterviews) { Element interviewsNode = studyNode.addElement("interviews"); List<Interview> interviews = Interviews.getInterviewsForStudy(session, study.getId()); // IF the user selected which interviews in this study to include in the analysis // that data will be in List<CheckIncludeID> checkIncludeIDList. // need to remove unwanted interviews the 'safe' way if ( checkIncludeIDList != null ) { // First, copy interviews to a temp structure and clear original array List<Interview> oldInterviews = Lists.newArrayList(); // new ArrayList<Interview>(); oldInterviews.addAll(interviews); interviews.clear(); // now reconstruct the list of interviews to include for ( Interview interview : oldInterviews ) { // System.out.println ( "interviewID = " + interview.getId()); if ( CheckIncludeID.useThisID(checkIncludeIDList, interview.getId())) interviews.add(interview); } } for(Interview interview : interviews ) { addInterviewNode(interviewsNode,interview, Alters.getForInterview(session, interview.getId()), Answers.getAnswersForInterview(session, interview.getId())); } } return formatXMLDocument(document); } catch(Exception ex) { throw new RuntimeException("Failed to get XML for study "+study, ex); } } public static Study loadStudyXML(final Study study, final String studyXML, final String name) { return new DB.Action<Study>() { public Study get() { return loadStudyXML(session, study,studyXML,name,true,false); } }.execute(); } public static Study loadRespondentXML(final Study study, final String studyXML) { return new DB.Action<Study>() { public Study get() { return loadStudyXML(session, study,studyXML,null,false,true); } }.execute(); } public static Study loadStudyXML(final Session session, Study studyToUpdate, String studyXML, String newName, Boolean updateStudy, Boolean updateRespondentData) { try { Document document = new SAXReader().read(new StringReader(studyXML)); Element studyElement = document.getRootElement(); Study study = studyToUpdate; if(study == null) { study = new Study(); } else { Long xmlKey = attrLong(studyElement,"key"); if(xmlKey == null || ! xmlKey.equals(study.getRandomKey())) { throw new RuntimeException("Trying to import incompatible study."); } } DB.save(session, study); List<Element> expressionElements = studyElement.element("expressions").elements("expression"); List<Expression> expressions = Expressions.forStudy(session, study.getId()); Map<Long,Long> remoteToLocalExpressionId = createRemoteToLocalMap( expressions, expressionElements, updateStudy, updateStudy, fnCreateExpression(), fnDeleteExpression(session)); if(updateStudy) { updateStudyFromNode(session,study,studyElement,remoteToLocalExpressionId,newName); } // older XML files might not have the answerLists section // in this case pass over the answerList updates, they will get // initialized from the presets List<Element> answerListElements; try { answerListElements = studyElement.element("answerLists").elements("answerList"); } catch ( java.lang.NullPointerException npe ) { answerListElements = null; } if ( answerListElements != null ) { for ( Element element : answerListElements ) { updateAnswerListFromNode(session, new AnswerList(), element, study.getId()); } } List<Element> questionElements = studyElement.element("questions").elements("question"); List<Question> questions = Questions.getQuestionsForStudy(session, study.getId(), null); Map<Long,Long> remoteToLocalQuestionId = createRemoteToLocalMap( questions, questionElements, updateStudy, updateStudy, fnCreateQuestion(), fnDeleteQuestion(session)); Map<Long,Long> remoteToLocalOptionId = Maps.newTreeMap(); Map<Long,Question> localIdToQuestion = indexById(questions); for(Element questionElement : questionElements) { Long remoteQuestionId = attrId(questionElement); Long localQuestionId = remoteToLocalQuestionId.get(remoteQuestionId); if(localQuestionId != null) { Question question = localIdToQuestion.get(localQuestionId); if(question == null) { String msg = "LocalQuestionId is "+localQuestionId+" but no local question? "; msg += "Remote to local map: "; for(Map.Entry<Long,Long> keyVal : remoteToLocalQuestionId.entrySet()) { msg += " <"+keyVal.getKey()+","+keyVal.getValue()+">, "; } msg += "Ids in local map: "; for(Long key : localIdToQuestion.keySet()) { msg += " "+key+", "; } throw new RuntimeException(msg); } if(updateStudy) { updateQuestionFromNode(session,question,questionElement, study.getId(),remoteToLocalExpressionId,remoteToLocalQuestionId); } List<Element> optionElements = questionElement.elements("option"); List<QuestionOption> optionEntities = Options.getOptionsForQuestion(session, question.getId()); Map<Long,Long> optionIdMap = createRemoteToLocalMap( optionEntities,optionElements,updateStudy,updateStudy, fnCreateOption(),fnDeleteOption(session)); remoteToLocalOptionId.putAll(optionIdMap); if(updateStudy) { Map<Long,QuestionOption> idToOptionEntity = indexById(optionEntities); for(Element optionElement : optionElements) { Long localId = remoteToLocalOptionId.get(attrId(optionElement)); if(localId != null) { QuestionOption optionEntity = idToOptionEntity.get(localId); updateOptionFromNode(session,optionEntity,optionElement, study,question); } } } } } if(updateStudy) { Map<Long,Expression> localIdToExpression = indexById(expressions); for(Element expressionElement : expressionElements) { Long localExpressionId = remoteToLocalExpressionId.get(attrId(expressionElement)); if(localExpressionId != null) { Expression expression = localIdToExpression.get(localExpressionId); updateExpressionFromNode(session,expression,expressionElement, study.getId(),remoteToLocalQuestionId,remoteToLocalOptionId, remoteToLocalExpressionId); } } } if(updateRespondentData) { // Import interviews List<Element> interviewElements = studyElement.element("interviews").elements("interview"); List<Interview> interviews = Interviews.getInterviewsForStudy(session, study.getId()); Map<Long,Long> remoteToLocalInterviewId = createRemoteToLocalMap( interviews, interviewElements, true, false, fnCreateInterview(), null); Map<Long,Interview> localIdToInterview = indexById(interviews); for(Element interviewElement : interviewElements) { Long localInterviewId = remoteToLocalInterviewId.get(attrId(interviewElement)); Interview interview = localIdToInterview.get(localInterviewId); updateInterviewFromNode(session,interview,interviewElement,study.getId()); // Import alters List<Element> alterElements = interviewElement.element("alters").elements("alter"); List<Alter> alterEntities = Alters.getForInterview(session, interview.getId()); Map<Long,Long> remoteToLocalAlterId = createRemoteToLocalMap( alterEntities, alterElements, true, true, fnCreateAlter(), fnDeleteAlter(session)); Map<Long,Alter> localIdToAlter = indexById(alterEntities); for(Integer i = 0; i < alterElements.size(); i++) { Element alterElement = alterElements.get(i); Long localAlterId = remoteToLocalAlterId.get(attrId(alterElement)); Alter alter = localIdToAlter.get(localAlterId); alter.setOrdering(i); updateAlterFromNode(session,alter,alterElement,interview.getId()); } // Import answers List<Element> answerElements = interviewElement.element("answers").elements("answer"); List<Answer> answerEntities = Answers.getAnswersForInterview(session, interview.getId()); Map<Long,Long> remoteToLocalAnswerId = createRemoteToLocalMap( answerEntities, answerElements, true, false, fnCreateAnswer(), null); Map<Long,Answer> localIdToAnswer = indexById(answerEntities); for(Element answerElement : answerElements) { Long localAnswerId = remoteToLocalAnswerId.get(attrId(answerElement)); Answer answer = localIdToAnswer.get(localAnswerId); updateAnswerFromNode(session,answer,answerElement, study.getId(),interview.getId(), remoteToLocalQuestionId,remoteToLocalAlterId,remoteToLocalOptionId); } } } return study; } catch(Exception ex) { throw new RuntimeException("Failed to load XML for study "+studyToUpdate, ex); } } private static <E extends Entity> Map<Long,Long> createRemoteToLocalMap( List<E> entities, List<Element> elements, Boolean shouldCreate, Boolean shouldDelete, Function<Object,E> creator, Function<E,Object> deleter) { // Index entities by key Set<Long> keys = Sets.newTreeSet(); Map<Long,E> keyToEntity = Maps.newTreeMap(); for(E entity : entities) { keyToEntity.put(entity.getRandomKey(), entity); keys.add(entity.getRandomKey()); } Map<Long,Element> keyToElement = Maps.newTreeMap(); for(Element element : elements) { Long key = attrLong(element,"key"); keyToElement.put(key, element); keys.add(key); } // Create map from remote id to local id Map<Long,Long> remoteToLocalId = Maps.newTreeMap(); for(Long key : keys) { if(shouldCreate && ! keyToEntity.containsKey(key)) { E entity = creator.apply(null); entity.setRandomKey(key); DB.save(entity); keyToEntity.put(key, entity); entities.add(entity); } if(shouldDelete && ! keyToElement.containsKey(key)) { E entity = keyToEntity.remove(key); deleter.apply(entity); entities.remove(entity); } Entity entity = keyToEntity.get(key); Element element = keyToElement.get(key); if(entity != null && element != null) { remoteToLocalId.put(attrId(element), entity.getId()); } } return remoteToLocalId; } private static Element addStudyNode(Document document, Study study) { Element studyNode = document.addElement("study"); addAttribute(studyNode,"id", study.getId()); addAttribute(studyNode,"name", study.getName()); addAttribute(studyNode,"key", study.getRandomKey()+""); addAttribute(studyNode,"minAlters", study.getMinAlters()+""); addAttribute(studyNode,"maxAlters", study.getMaxAlters()+""); addAttribute(studyNode,"valueDontKnow", study.getValueDontKnow()); addAttribute(studyNode,"valueLogicalSkip", study.getValueLogicalSkip()); addAttribute(studyNode,"valueNotYetAnswered", study.getValueNotYetAnswered()); addAttribute(studyNode,"valueRefusal", study.getValueRefusal()); addAttribute(studyNode,"adjacencyExpressionId", study.getAdjacencyExpressionId()); addText(studyNode,"introduction",study.getIntroduction()); addText(studyNode,"egoIdPrompt",study.getEgoIdPrompt()); addText(studyNode,"alterPrompt",study.getAlterPrompt()); addText(studyNode,"conclusion",study.getConclusion()); return studyNode; } private static void updateStudyFromNode(Session session, Study study, Element studyElement, Map<Long,Long> remoteToLocalExpressionId, String newName) { study.setName(newName == null || newName.isEmpty() ? attrString(studyElement,"name") : newName); study.setRandomKey(attrLong(studyElement,"key")); study.setMinAlters(attrInt(studyElement,"minAlters")); study.setMaxAlters(attrInt(studyElement,"maxAlters")); study.setValueDontKnow(attrString(studyElement,"valueDontKnow")); study.setValueLogicalSkip(attrString(studyElement,"valueLogicalSkip")); study.setValueNotYetAnswered(attrString(studyElement,"valueNotYetAnswered")); study.setValueRefusal(attrString(studyElement,"valueRefusal")); Long remoteAdjacencyId = attrLong(studyElement,"adjacencyExpressionId"); study.setAdjacencyExpressionId( remoteAdjacencyId == null ? null : remoteToLocalExpressionId.get(remoteAdjacencyId)); study.setIntroduction(attrText(studyElement,"introduction")); study.setEgoIdPrompt(attrText(studyElement,"egoIdPrompt")); study.setAlterPrompt(attrText(studyElement,"alterPrompt")); study.setConclusion(attrText(studyElement,"conclusion")); DB.save(session, study); } private static Element addQuestionNode(Element questionsNode, Question question, List<QuestionOption> options, Integer ordering) { Answer.AnswerType aType; Element questionNode = questionsNode.addElement("question"); addAttribute(questionNode,"id", question.getId()); addAttribute(questionNode,"title", question.getTitle()); addAttribute(questionNode,"key", question.getRandomKey()); addAttribute(questionNode,"answerType", question.getAnswerTypeDB()); addAttribute(questionNode,"subjectType", question.getTypeDB()); addAttribute(questionNode,"askingStyleList", question.getAskingStyleList()); // in case ordering == null, I use the order they were pulled from the DB addAttribute(questionNode,"ordering", ordering); addAttribute(questionNode,"answerReasonExpressionId", question.getAnswerReasonExpressionId()); // addAttribute(questionNode,"useIf", question.getUseIfExpression()); no longer used! addAttribute(questionNode,"otherSpecify", question.getOtherSpecify()); addAttribute(questionNode,"noneButton", question.getNoneButton()); addAttribute(questionNode,"allButton", question.getAllButton()); addAttribute(questionNode,"pageLevelDontKnowButton", question.getPageLevelDontKnowButton()); addAttribute(questionNode,"pageLevelRefuseButton", question.getPageLevelRefuseButton()); addAttribute(questionNode,"dontKnowButton", question.getDontKnowButton()); addAttribute(questionNode,"networkRelationshipExprId", question.getNetworkRelationshipExprId()); addAttribute(questionNode,"networkNShapeQId", question.getNetworkNShapeQId()); addAttribute(questionNode,"networkNColorQId", question.getNetworkNColorQId()); addAttribute(questionNode,"networkNSizeQId", question.getNetworkNSizeQId()); addAttribute(questionNode,"networkEColorQId", question.getNetworkEColorQId()); addAttribute(questionNode,"networkESizeQId", question.getNetworkESizeQId()); addAttribute(questionNode,"refuseButton", question.getRefuseButton()); addAttribute(questionNode,"allOptionString", question.getAllOptionString()); addAttribute(questionNode,"symmetric", question.getSymmetric()); addAttribute(questionNode,"keepOnSamePage", question.getKeepOnSamePage()); aType = question.getAnswerType(); if (aType==Answer.AnswerType.NUMERICAL ) { addAttribute(questionNode,"minLimitType", question.getMinLimitTypeDB()); addAttribute(questionNode,"minLiteral", question.getMinLiteral()); addAttribute(questionNode,"minPrevQues", question.getMinPrevQues()); addAttribute(questionNode,"maxLimitType", question.getMaxLimitTypeDB()); addAttribute(questionNode,"maxLiteral", question.getMaxLiteral()); addAttribute(questionNode,"maxPrevQues", question.getMaxPrevQues()); } else if (aType==Answer.AnswerType.MULTIPLE_SELECTION ) { addAttribute(questionNode,"minCheckableBoxes", question.getMinCheckableBoxes()); addAttribute(questionNode,"maxCheckableBoxes", question.getMaxCheckableBoxes()); } else if ( aType==Answer.AnswerType.DATE || aType==Answer.AnswerType.TIME_SPAN ) { addAttribute(questionNode,"timeUnits", question.getTimeUnits()); } if ( aType==Answer.AnswerType.MULTIPLE_SELECTION || aType==Answer.AnswerType.SELECTION) { addAttribute(questionNode,"withListRange", question.getWithListRange()); addAttribute(questionNode,"listRangeString", question.getListRangeString()); addAttribute(questionNode,"minListRange", question.getMinListRange()); addAttribute(questionNode,"maxListRange", question.getMaxListRange()); } addText(questionNode,"preface",question.getPreface()); addText(questionNode,"prompt",question.getPrompt()); addText(questionNode,"citation",question.getCitation()); for(Integer i = 0; i < options.size(); i++) { addOptionNode(questionNode,options.get(i),i); } return questionNode; } /* * Ethan - 20100625: modified the parameter list for updateQuestionFromNode to add remoteToLocalQuestionId, so * that the network questions can load the IDs of the questions being used to customize * the node/edge rendering of the graph. If there's any trouble retrieving values from * remoteToLocalQuestionId, the network question parameters are all set to null. */ /** * when importing older xml files, the data regarding the numeric checking * and ranges might not be present. If an exception is thrown we will just * assign default values. * @param session * @param question * @param node * @param studyId * @param remoteToLocalExpressionId * @param remoteToLocalQuestionId */ private static void updateQuestionFromNode(Session session, Question question, Element node, Long studyId, Map<Long,Long> remoteToLocalExpressionId, Map<Long,Long>remoteToLocalQuestionId) { Answer.AnswerType aType; question.setStudyId(studyId); question.setTitle(attrString(node,"title")); question.setAnswerTypeDB(attrString(node,"answerType")); question.setTypeDB(attrString(node,"subjectType")); question.setAskingStyleList(attrBool(node,"askingStyleList")); question.setOrdering(attrInt(node,"ordering")); question.setPreface(attrText(node,"preface")); question.setPrompt(attrText(node,"prompt")); question.setCitation(attrText(node,"citation")); // question.setUseIfExpression(attrString(node,"useIf")); No longer used! try { question.setOtherSpecify(attrBool(node,"otherSpecify")); } catch ( java.lang.RuntimeException rte2 ) { question.setOtherSpecify(false); } try { question.setSymmetric(attrBool(node,"symmetric")); } catch ( java.lang.RuntimeException rte5 ) { question.setSymmetric(false); } try { question.setKeepOnSamePage(attrBool(node,"keepOnSamePage")); } catch ( java.lang.RuntimeException rte6 ) { question.setKeepOnSamePage(false); } aType = question.getAnswerType(); if ( aType==Answer.AnswerType.NUMERICAL ) { try { question.setMinLimitTypeDB(attrString(node,"minLimitType")); question.setMinLiteral(attrInt(node,"minLiteral")); question.setMinPrevQues(attrString(node,"minPrevQues")); question.setMaxLimitTypeDB(attrString(node,"maxLimitType")); question.setMaxLiteral(attrInt(node,"maxLiteral")); question.setMaxPrevQues(attrString(node,"maxPrevQues")); } catch ( java.lang.RuntimeException rte ) { // if just about anything went wrong, goto defaults question.setMinLimitType(NumericLimitType.NLT_NONE); question.setMinLiteral(0); question.setMinPrevQues(""); question.setMaxLimitType(NumericLimitType.NLT_NONE); question.setMaxLiteral(1000); question.setMaxPrevQues(""); } } if ( aType==Answer.AnswerType.MULTIPLE_SELECTION ) { try { question.setMinCheckableBoxes(attrInt(node,"minCheckableBoxes")); question.setMaxCheckableBoxes(attrInt(node,"maxCheckableBoxes")); } catch ( java.lang.RuntimeException rte2 ) { // if anything went wrong, assign reasonable defaults question.setMinCheckableBoxes(0); question.setMaxCheckableBoxes(100); } } if ( aType==Answer.AnswerType.MULTIPLE_SELECTION || aType==Answer.AnswerType.SELECTION) { try { question.setOtherSpecify(attrBool(node,"otherSpecify")); question.setWithListRange(attrBool(node,"withListRange")); question.setListRangeString(attrString(node,"listRangeString")); question.setMinListRange(attrInt(node,"minListRange")); question.setMaxListRange(attrInt(node,"maxListRange")); } catch ( java.lang.RuntimeException rte3 ) { question.setOtherSpecify(false); question.setWithListRange(false); question.setListRangeString(""); question.setMinListRange(0); question.setMaxListRange(100); } } try { question.setNoneButton(attrBool(node,"noneButton")); question.setAllButton(attrBool(node,"allButton")); question.setPageLevelDontKnowButton(attrBool(node,"pageLevelDontKnowButton")); question.setPageLevelRefuseButton (attrBool(node,"pageLevelRefuseButton")); question.setDontKnowButton(attrBool(node,"dontKnowButton")); question.setRefuseButton(attrBool(node,"refuseButton")); question.setAllOptionString(attrString(node,"allOptionString")); } catch ( java.lang.RuntimeException rte4 ) { question.setNoneButton(new Boolean(false)); question.setAllButton(new Boolean(false)); question.setPageLevelDontKnowButton(new Boolean(true)); question.setPageLevelRefuseButton (new Boolean(true)); question.setDontKnowButton(new Boolean(true)); question.setRefuseButton(new Boolean(true)); question.setAllOptionString(new String("")); } if ( aType==Answer.AnswerType.DATE || aType==Answer.AnswerType.TIME_SPAN ) { try { question.setTimeUnits(attrInt(node,"timeUnits")); } catch ( java.lang.RuntimeException rte4 ) { question.setTimeUnits(0xff); } } Long remoteReasonId = attrLong(node,"answerReasonExpressionId"); question.setAnswerReasonExpressionId( remoteReasonId == null ? null : remoteToLocalExpressionId.get(remoteReasonId)); try { Long remoteNetworkRelationshipId = attrLong(node,"networkRelationshipExprId"); question.setNetworkRelationshipExprId( remoteNetworkRelationshipId == null ? null : remoteToLocalExpressionId.get(remoteNetworkRelationshipId)); Long nShapeQId = attrLong(node, "networkNShapeQId"); question.setNetworkNShapeQId(nShapeQId == null? null : remoteToLocalQuestionId.get(nShapeQId)); Long nSizeQId = attrLong(node, "networkNSizeQId"); question.setNetworkNSizeQId(nSizeQId == null? null : remoteToLocalQuestionId.get(nSizeQId)); Long nColorQId = attrLong(node, "networkNColorQId"); question.setNetworkNColorQId(nColorQId == null? null : remoteToLocalQuestionId.get(nColorQId)); Long eColorQId = attrLong(node, "networkEColorQId"); question.setNetworkEColorQId(eColorQId == null? null : remoteToLocalQuestionId.get(eColorQId)); Long eSizeQId = attrLong(node, "networkESizeQId"); question.setNetworkESizeQId(eSizeQId == null? null : remoteToLocalQuestionId.get(eSizeQId)); } catch (java.lang.RuntimeException rte) { question.setNetworkRelationshipExprId(null); question.setNetworkNShapeQId(null); question.setNetworkNColorQId(null); question.setNetworkNSizeQId(null); question.setNetworkEColorQId(null); question.setNetworkESizeQId(null); } DB.save(session, question); } private static Element addOptionNode(Element questionNode, QuestionOption option, Integer ordering) { Element optionNode = questionNode.addElement("option"); addAttribute(optionNode,"id", option.getId()); addAttribute(optionNode,"name", option.getName()); addAttribute(optionNode,"key", option.getRandomKey()); addAttribute(optionNode,"value", option.getValue()); addAttribute(optionNode,"ordering", ordering); return optionNode; } private static void updateOptionFromNode(Session session, QuestionOption option, Element node, Study study, Question question) { option.setQuestionId(question.getId()); option.setName(attrString(node,"name")); option.setValue(attrString(node,"value")); option.setStudyId(study.getId()); option.setOrdering(attrInt(node,"ordering")); DB.save(session, option); } private static Element addExpressionNode(Element parent, Expression expression) { Element expressionNode = parent.addElement("expression"); addAttribute(expressionNode,"id", expression.getId()); addAttribute(expressionNode,"name", expression.getName()); addAttribute(expressionNode,"key", expression.getRandomKey()); addAttribute(expressionNode,"questionId", expression.getQuestionId()); addAttribute(expressionNode,"resultForUnanswered", expression.getResultForUnanswered()); addAttribute(expressionNode,"type", expression.getTypeDB()); addAttribute(expressionNode,"operator", expression.getOperatorDB()); addText(expressionNode,"value",expression.getValueDB()); return expressionNode; } private static void updateExpressionFromNode(Session session, Expression expression, Element node, Long studyId, Map<Long,Long> remoteToLocalQuestionId, Map<Long,Long> remoteToLocalOptionId, Map<Long,Long> remoteToLocalExpressionId) { expression.setStudyId(studyId); expression.setName(attrString(node,"name")); expression.setResultForUnanswered(attrBool(node,"resultForUnanswered")); expression.setTypeDB(attrString(node,"type")); expression.setOperatorDB(attrString(node,"operator")); // questionId Long remoteQuestionId = attrLong(node,"questionId"); expression.setQuestionId( remoteQuestionId == null ? null : remoteToLocalQuestionId.get(remoteQuestionId)); // value (first set as normal, then convert IDs) expression.setValueDB(attrText(node,"value")); if(expression.getType().equals(Expression.Type.Selection) || expression.getType().equals(Expression.Type.Compound)) { List<Long> localValueIds = Lists.newArrayList(); for(Long remoteValueId : (List<Long>) expression.getValue()) { Map<Long,Long> remoteToLocalValueId = expression.getType().equals(Expression.Type.Compound) ? remoteToLocalExpressionId : remoteToLocalOptionId; Long localValueId = remoteToLocalValueId.get(remoteValueId); if(localValueId != null) { localValueIds.add(localValueId); } } expression.setValue(localValueIds); } else if(expression.getType().equals(Expression.Type.Comparison)) { Pair<Integer,Long> remoteNumberExpr = (Pair<Integer,Long>) expression.getValue(); expression.setValue(new Pair<Integer,Long>( remoteNumberExpr.getFirst(), remoteToLocalExpressionId.get(remoteNumberExpr.getSecond()))); } else if(expression.getType().equals(Expression.Type.Counting)) { Triple<Integer,List<Long>,List<Long>> remoteNumberExprsQuests = (Triple<Integer,List<Long>,List<Long>>) expression.getValue(); List<Long> localExprs = Lists.newArrayList(); for(Long remoteExpr : remoteNumberExprsQuests.getSecond()) { Long localExpr = remoteToLocalExpressionId.get(remoteExpr); if(localExpr != null) { localExprs.add(localExpr); } } List<Long> localQuests = Lists.newArrayList(); for(Long remoteQuest : remoteNumberExprsQuests.getThird()) { Long localQuest = remoteToLocalQuestionId.get(remoteQuest); if(localQuest != null) { localQuests.add(localQuest); } } expression.setValue(new Triple<Integer,List<Long>,List<Long>>( remoteNumberExprsQuests.getFirst(), localExprs, localQuests)); } DB.save(session, expression); } private static Element addInterviewNode(Element parent, Interview interview, List<Alter> alters, List<Answer> answers) { Element interviewNode = parent.addElement("interview"); addAttribute(interviewNode,"id", interview.getId()); addAttribute(interviewNode,"key", interview.getRandomKey()); addAttribute(interviewNode,"completed", interview.getCompleted()); Element altersNode = interviewNode.addElement("alters"); for(Alter alter : alters) { addAlterNode(altersNode,alter); } Element answersNode = interviewNode.addElement("answers"); for(Answer answer : answers) { addAnswerNode(answersNode,answer); } return interviewNode; } private static void updateInterviewFromNode(Session session, Interview interview, Element node, Long studyId) { interview.setStudyId(studyId); try { interview.setCompleted(attrBool(node,"completed")); } catch (java.lang.RuntimeException rte) { interview.setCompleted(new Boolean(false));} DB.save(session, interview); } private static Element addAlterNode(Element parent, Alter alter) { Element alterNode = parent.addElement("alter"); addAttribute(alterNode,"id", alter.getId()); addAttribute(alterNode,"name", alter.getName()); addAttribute(alterNode,"key", alter.getRandomKey()+""); return alterNode; } private static void updateAlterFromNode(Session session, Alter alter, Element node, Long interviewId) { alter.setInterviewId(interviewId); alter.setName(attrString(node,"name")); DB.save(session, alter); } private static Element addAnswerNode(Element parent, Answer answer) { Element answerNode = parent.addElement("answer"); addAttribute(answerNode,"id", answer.getId()); addAttribute(answerNode,"key", answer.getRandomKey()); addAttribute(answerNode,"questionId", answer.getQuestionId()); addAttribute(answerNode,"questionType", answer.getQuestionTypeDB()); addAttribute(answerNode,"skipReason", answer.getSkipReasonDB()); addAttribute(answerNode,"answerType", answer.getAnswerTypeDB()); addAttribute(answerNode,"alterId1", answer.getAlterId1()); addAttribute(answerNode,"alterId2", answer.getAlterId2()); addAttribute(answerNode,"otherSpecifyText", answer.getOtherSpecifyText()); addText(answerNode,"value",answer.getValue()); return answerNode; } private static void updateAnswerFromNode(Session session, Answer answer, Element node, Long studyId, Long interviewId, Map<Long,Long> remoteToLocalQuestionId, Map<Long,Long> remoteToLocalAlterId, Map<Long,Long> remoteToLocalOptionId) { answer.setStudyId(studyId); answer.setInterviewId(interviewId); answer.setQuestionTypeDB(attrString(node,"questionType")); answer.setSkipReasonDB(attrString(node,"skipReason")); answer.setAnswerTypeDB(attrString(node,"answerType")); // questionId Long remoteQuestionId = attrLong(node,"questionId"); answer.setQuestionId( remoteQuestionId == null ? null : remoteToLocalQuestionId.get(remoteQuestionId)); // alterId1 Long remoteAlterId1 = attrLong(node,"alterId1"); answer.setAlterId1( remoteAlterId1 == null ? null : remoteToLocalAlterId.get(remoteAlterId1)); // alterId2 Long remoteAlterId2 = attrLong(node,"alterId2"); answer.setAlterId2( remoteAlterId2 == null ? null : remoteToLocalAlterId.get(remoteAlterId2)); // value (requires translation - see Archiving.updateExpressionFromNode and MultipleSelectionAnswerFormFieldPanel) String answerString = attrText(node,"value"); Answer.AnswerType answerType = answer.getAnswerType(); if(answerType.equals(Answer.AnswerType.SELECTION) || answerType.equals(Answer.AnswerType.MULTIPLE_SELECTION)) { String optionIds = ""; try { for(String optionRemoteIdString : answerString.split(",")) { Long optionRemoteId = Long.parseLong(optionRemoteIdString); Long optionLocalId = remoteToLocalOptionId.get(optionRemoteId); if(optionLocalId != null) { optionIds += (optionIds.isEmpty() ? "" : ",")+optionLocalId; } } } catch(Exception ex) { // Most likely failed to parse answer. Fall back to no existing answer. } answer.setValue(optionIds); } else { answer.setValue(answerString); } answer.setOtherSpecifyText(attrString(node,"otherSpecifyText")); DB.save(session, answer); } private static String formatXMLDocument(Document document) throws IOException { StringWriter stringWriter = new StringWriter(); OutputFormat format = OutputFormat.createPrettyPrint(); format.setNewlines(true); format.setTrimText(false); format.setLineSeparator("\r\n"); new XMLWriter(stringWriter,format).write(document); return stringWriter.toString(); } private static <E extends Entity> Map<Long,E> indexById(List<E> entities) { Map<Long,E> idToEntity = Maps.newTreeMap(); for(E entity : entities) { idToEntity.put(entity.getId(), entity); } return idToEntity; } private static String attrText(Element element, String name) { Element textElement = element.element(name); if(textElement == null) { return null; } return textElement.getText(); } private static String attrString(Element element, String name) { if(element == null || name == null) { throw new RuntimeException( "Unable to determine "+name+" attribute for "+ (element == null ? "null " : "")+"element."); } Attribute attribute = element.attribute(name); if(attribute == null) { String others = ""; for(Object attrObj : element.attributes()) { Attribute attr = (Attribute) attrObj; others += ((others.isEmpty() ? "" : ", ") + "[" + (attr.getName()+" : "+attr.getValue()) + "]"); } throw new RuntimeException("Element does not contain the requested attribute "+name+ ", but does contain: "+others); } String attr = attribute.getValue(); return attr == null || attr.isEmpty() || attr.equals("null") ? null : attr; } private static Long attrLong(Element element, String name) { String str = attrString(element,name); return str == null ? null : Long.parseLong(str); } private static Long attrId(Element element) { return attrLong(element,"id"); } private static Integer attrInt(Element element, String name) { String str = attrString(element,name); return str == null ? null : Integer.parseInt(str); } private static Boolean attrBool(Element element, String name) { String str = attrString(element,name); if(str == null) { return null; } if(str.equalsIgnoreCase("true")) { return true; } if(str.equalsIgnoreCase("false")) { return false; } return null; } private static Function<Object,Question> fnCreateQuestion() { return new Function<Object,Question>() { public Question apply(Object arg0) { return new Question(); } }; } private static Function<Object,QuestionOption> fnCreateOption() { return new Function<Object,QuestionOption>() { public QuestionOption apply(Object arg0) { return new QuestionOption(); } }; } private static Function<Object,Expression> fnCreateExpression() { return new Function<Object,Expression>() { public Expression apply(Object arg0) { return new Expression(); } }; } private static Function<Object,Interview> fnCreateInterview() { return new Function<Object,Interview>() { public Interview apply(Object arg0) { return new Interview(); } }; } private static Function<Object,Alter> fnCreateAlter() { return new Function<Object,Alter>() { public Alter apply(Object arg0) { return new Alter(); } }; } private static Function<Object,Answer> fnCreateAnswer() { return new Function<Object,Answer>() { public Answer apply(Object arg0) { return new Answer(); } }; } private static Function<Question,Object> fnDeleteQuestion(final Session session) { return new Function<Question,Object>() { public Object apply(Question question) { Questions.delete(session, question); return null; } }; } private static Function<QuestionOption,Object> fnDeleteOption(final Session session) { return new Function<QuestionOption,Object>() { public Object apply(QuestionOption option) { Options.delete(session, option); return null; } }; } private static Function<Expression,Object> fnDeleteExpression(final Session session) { return new Function<Expression,Object>() { public Object apply(Expression expression) { Expressions.delete(session, expression); return null; } }; } private static Function<Alter,Object> fnDeleteAlter(final Session session) { return new Function<Alter,Object>() { public Object apply(Alter alter) { Alters.delete(session, alter); return null; } }; } private static Element addAnswerListNode(Element parent, AnswerList answerList) { Element answerListNode = parent.addElement("answerList"); addAttribute(answerListNode,"id", answerList.getId()); addAttribute(answerListNode,"key", answerList.getRandomKey()); addAttribute(answerListNode,"listName", answerList.getListName()); addAttribute(answerListNode,"listOptionNames", answerList.getListOptionNamesDB()); return answerListNode; } private static void updateAnswerListFromNode(Session session, AnswerList answerList, Element node, Long studyId ) { answerList.setStudyId(studyId); answerList.setListName(attrString(node,"listName")); answerList.setListOptionNamesDB(attrString(node,"listOptionNames")); DB.save(session, answerList); } /** * this creates a plain text report, * but might be altered to XML format. * This creates the report dealing with the Other/Specify types of questions * and their answers * @param study to examine * @return a huge string containing the report */ public static String getOtherSpecifyReport ( Study study ) { String cr = System.getProperty("line.separator"); Question.QuestionType qType; Long[] ids; StringBuilder strb = new StringBuilder(); List<Question> listOfQuestions; List<Answer> listOfAnswers; AnswerSorter answerSorter = new AnswerSorter(); String otherSpecText; strb.append("Other/Specify text responses for " + study.getName() + cr); listOfQuestions = Questions.getQuestionsWithOtherSpecifyForStudy(study.getId()); for ( Question.QuestionType questType : Question.QuestionType.values() ) { for ( Question question : listOfQuestions ) { qType = question.getType(); if ( qType.equals(questType )) { answerSorter.newQuestion(); listOfAnswers = Answers.getAnswersForQuestion(question.getId()); if ( answerSorter.addData(listOfAnswers)) { strb.append(cr); strb.append( " QUESTION: " + question.getTitle() + " (" + qType.toString() + ")" + cr); strb.append( "QUESTION PROMPT: " + question.getPrompt() + cr); ids = answerSorter.getIDs(); for ( Long interviewId : ids) { strb.append (" EGO ID: " + Interviews.getEgoNameForInterview(interviewId) + cr); listOfAnswers = answerSorter.getAnswersForID(interviewId); for ( Answer answer : listOfAnswers ) { otherSpecText = answer.getOtherSpecifyText(); strb.append(" SPECIFY TEXT: " + otherSpecText + cr); switch ( qType ) { case ALTER: strb.append( " ALTER ID: " + answer.getAlterId1() + cr); break; case ALTER_PAIR: strb.append(" ALTER PAIR: " + answer.getAlterId1() + " , " + answer.getAlterId2() + cr); break; } // end case ( qType... } // end for ( Answer ... } // end for ( Long interviewId } // end if ( answerSorter.addData... } // end if ( qType.equals... } // end for question... } // end for questType... return(strb.toString()); } } /** * a simple convenience class to sort answers to a question * by the interview ID. Used exclusively by getOtherSpecifyReport * */ class AnswerSorter { private TreeMap <Long,ArrayList<Answer>> answersByEgoId; private boolean anyData; public AnswerSorter() { answersByEgoId = new TreeMap<Long, ArrayList<Answer>>(); anyData = false; } public void newQuestion() { answersByEgoId.clear(); anyData = false; } public boolean addData ( List<Answer> listOfAnswers ) { ArrayList<Answer> answerList; String otherSpecText; Long id; if ( listOfAnswers==null || listOfAnswers.isEmpty()) return(anyData); for ( Answer answer : listOfAnswers ) { otherSpecText = answer.getOtherSpecifyText(); if ( otherSpecText!=null && otherSpecText.length()>0 ) { id = answer.getInterviewId(); if ( !answersByEgoId.containsKey(id)) { answerList = new ArrayList<Answer>(); answersByEgoId.put(id, answerList); } else { answerList = answersByEgoId.get(id); } if ( answerList != null ) { answerList.add(answer); anyData = true; } } } return(anyData); } public boolean isAnyData() { return ( anyData);} public Long[] getIDs() { Set<Long> keySet = answersByEgoId.keySet(); Long[] returnArray = new Long[keySet.size()]; returnArray = keySet.toArray(returnArray); return(returnArray); } public ArrayList<Answer> getAnswersForID ( Long id ) { return ( answersByEgoId.get(id)); } }