Skip to content

Commit

Permalink
CARDS-1937: Computed answers: implement deletion the value of depende…
Browse files Browse the repository at this point in the history
…nt on change reference answers computed answers
  • Loading branch information
sofi2002sofi committed Feb 4, 2023
1 parent 2d1f679 commit 6859057
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ default String evaluate(Node question, Map<String, Object> values)
* expression has unmet dependencies or the actual evaluation result cannot be converted to the desired type
*/
Object evaluate(Node question, Map<String, Object> values, Type<?> type);

/**
*
* @param question the question node
* @return list of all the questions names that is used to compute an answer
*/
Set<String> getQuestionsNames(Node question);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.uhndata.cards.forms.api.FormUtils;
import io.uhndata.cards.forms.api.QuestionnaireUtils;
Expand All @@ -48,6 +49,7 @@
*/
public abstract class AnswersEditor extends DefaultEditor
{
private static final Logger LOGGER = LoggerFactory.getLogger(AnswersEditor.class);
// This holds the builder for the current node. The methods called for editing specific properties don't receive the
// actual parent node of those properties, so we must manually keep track of the current node.
protected final NodeBuilder currentNodeBuilder;
Expand Down Expand Up @@ -154,6 +156,16 @@ protected Node getQuestionnaire()
}
}

protected Node getForm()
{
final String formId = this.currentNodeBuilder.getProperty("jcr:uuid").getValue(Type.STRING);
try {
return this.serviceSession.getNodeByIdentifier(formId);
} catch (RepositoryException e) {
return null;
}
}

// Returns a QuestionTree if any children of this node contains an unanswered matching question, else null
protected QuestionTree getUnansweredMatchingQuestions(final Node currentNode)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ private void computeAnswer(final Map.Entry<Node, NodeBuilder> entry,
@SuppressWarnings("unchecked")
Type<Object> untypedResultType = (Type<Object>) resultType;
answer.setProperty(FormUtils.VALUE_PROPERTY, result, untypedResultType);
Set<String> computedFromQuestionPaths =
getQuestionPathsFromNames(this.expressionUtils.getQuestionsNames(question));
answer.setProperty("computedFrom", computedFromQuestionPaths, Type.STRINGS);
}
// Update the computed value in the map of existing answers
String questionName = this.questionnaireUtils.getQuestionName(question);
Expand All @@ -186,6 +189,23 @@ private void computeAnswer(final Map.Entry<Node, NodeBuilder> entry,
}
}

private Set<String> getQuestionPathsFromNames(final Set<String> names)
{
Set<String> paths = new HashSet<>();
Node formNode = getForm();
Node questionnaire = getQuestionnaire();
try {
for (String computedFromQuestionName : names) {
Node questionNode = this.questionnaireUtils.getQuestion(questionnaire, computedFromQuestionName);
Node changingAnswer = this.formUtils.getAnswer(formNode, questionNode);
paths.add(changingAnswer.getPath());
}
} catch (RepositoryException e) {
LOGGER.error("Error getting path of question. " + e.getMessage());
}
return paths;
}

private List<String> sortDependencies(final Map<String, Set<String>> computedAnswerDependencies)
{
final List<String> result = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -90,6 +91,43 @@ public Object evaluate(final Node question, final Map<String, Object> values, fi
return null;
}

@Override
public Set<String> getQuestionsNames(Node question)
{
return expressionInputs(getExpressionFromQuestion(question));
}

private Set<String> expressionInputs(final String expression)
{
String expr = expression;

Set<String> questionNames = new HashSet<>();

int start = expr.indexOf(START_MARKER);
int end = expr.indexOf(END_MARKER, start);

while (start > -1 && end > -1) {
int defaultStart = expr.indexOf(DEFAULT_MARKER, start);
boolean hasDefault = defaultStart > -1 && defaultStart < end;

String questionName;
if (hasDefault) {
questionName = expr.substring(start + START_MARKER.length(), defaultStart);
} else {
questionName = expr.substring(start + START_MARKER.length(), end);
}

questionNames.add(questionName);

// Remove the start and end tags
expr = expr.substring(0, start) + questionName + expr.substring(end + END_MARKER.length());

start = expr.indexOf(START_MARKER);
end = expr.indexOf(END_MARKER, start);
}
return questionNames;
}

private ExpressionUtilsImpl.ParsedExpression parseExpressionInputs(final String expression,
final Map<String, Object> values)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.uhndata.cards.forms.internal;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
Expand All @@ -28,6 +29,7 @@
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.version.VersionManager;

import org.apache.sling.api.resource.LoginException;
Expand Down Expand Up @@ -63,6 +65,12 @@ public class ReferenceAnswersChangedListener implements ResourceChangeListener
public static final String VALUE = "value";
private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceAnswersChangedListener.class);

/** Computed answers' paths that values need to be changed, with appropriate parent form path. **/
private final Map<String, String> toBeDeleted = new HashMap<>();

/** Set of changed reference answers' paths per form. **/
private final Map<Node, Set<String>> changedReferenceAnswers = new HashMap<>();

/** Provides access to resources. */
@Reference
private volatile ResourceResolverFactory resolverFactory;
Expand Down Expand Up @@ -107,7 +115,10 @@ private void handleEvent(final ResourceChange event)
try {
this.rrp.push(localResolver);
NodeIterator children = form.getNodes();
checkAndUpdateAnswersValues(children, localResolver, session);
final VersionManager versionManager = session.getWorkspace().getVersionManager();
checkAndUpdateAnswersValues(children, localResolver, session, versionManager);
searchComputedReferenceAnswers();
deleteComputedReferenceAnswersValues(session, versionManager);
} catch (RepositoryException e) {
LOGGER.error(e.getMessage(), e);
} finally {
Expand All @@ -131,14 +142,14 @@ private void handleEvent(final ResourceChange event)
* @param session a service session providing access to the repository
*/
private void checkAndUpdateAnswersValues(final NodeIterator nodeIterator, final ResourceResolver serviceResolver,
final Session session) throws RepositoryException
final Session session, final VersionManager versionManager)
throws RepositoryException
{
final VersionManager versionManager = session.getWorkspace().getVersionManager();
Set<String> checkoutPaths = new HashSet<>();
while (nodeIterator.hasNext()) {
final Node node = nodeIterator.nextNode();
if (node.isNodeType("cards:AnswerSection")) {
checkAndUpdateAnswersValues(node.getNodes(), serviceResolver, session);
checkAndUpdateAnswersValues(node.getNodes(), serviceResolver, session, versionManager);
} else if (node.hasProperty("sling:resourceSuperType")
&& "cards/Answer".equals(node.getProperty("sling:resourceSuperType").getString())) {
final Iterator<Resource> resourceIteratorReferencingAnswers = serviceResolver.findResources(
Expand All @@ -163,6 +174,7 @@ private void checkAndUpdateAnswersValues(final NodeIterator nodeIterator, final
Node changingAnswer = this.formUtils.getAnswer(formNode,
getQuestionNode(referenceAnswer, session, serviceResolver));
changingAnswer.setProperty(VALUE, sourceAnswerValue.getValue());
addChangedReferenceAnswer(formNode, referenceAnswer.getPath());
}
}
}
Expand All @@ -173,6 +185,58 @@ private void checkAndUpdateAnswersValues(final NodeIterator nodeIterator, final
}
}

private void searchComputedReferenceAnswers() throws RepositoryException
{
for (Map.Entry<Node, Set<String>> changedReferenceAnswersPerForm : this.changedReferenceAnswers.entrySet()) {
final Node formNode = changedReferenceAnswersPerForm.getKey();
final NodeIterator children = formNode.getNodes();
final Set<String> changedReferenceAnswersPaths = changedReferenceAnswersPerForm.getValue();
searchForComputedReferencesAnswers(formNode, children, changedReferenceAnswersPaths);
}
}

private void searchForComputedReferencesAnswers(final Node parentFormNode, final NodeIterator nodes,
final Set<String> changedReferenceAnswersPaths)
throws RepositoryException
{
while (nodes.hasNext()) {
final Node node = nodes.nextNode();
if (node.isNodeType("cards:AnswerSection")) {
searchForComputedReferencesAnswers(parentFormNode, node.getNodes(), changedReferenceAnswersPaths);
}

if (!node.hasProperty("computedFrom")) {
continue;
}
Value[] computedFromPropertyValues = node.getProperty("computedFrom").getValues();

for (Value computedFromPropertyValue : computedFromPropertyValues) {
if (changedReferenceAnswersPaths.contains(computedFromPropertyValue.getString())) {
continue;
}
this.toBeDeleted.put(node.getPath(), parentFormNode.getPath());
break;
}
}
}

private void deleteComputedReferenceAnswersValues(final Session session, final VersionManager versionManager)
throws RepositoryException
{
Set<String> checkoutPaths = new HashSet<>();
for (Map.Entry<String, String> changedComputedReferenceAnswer : this.toBeDeleted.entrySet()) {
final String formPath = changedComputedReferenceAnswer.getValue();
versionManager.checkout(formPath);
checkoutPaths.add(formPath);
Node changingComputerAnswer = session.getNode(changedComputedReferenceAnswer.getKey());
changingComputerAnswer.remove();
}
session.save();
for (String path : checkoutPaths) {
versionManager.checkin(path);
}
}

/**
* Gets the Question node for given Answer node.
*
Expand Down Expand Up @@ -213,4 +277,13 @@ private String getParentFormPath(Resource child)
}
return parent.getPath();
}

private void addChangedReferenceAnswer(final Node formNode, final String path)
{
if (this.changedReferenceAnswers.containsKey(formNode)) {
this.changedReferenceAnswers.get(formNode).add(path);
} else {
this.changedReferenceAnswers.put(formNode, Set.of(path));
}
}
}

0 comments on commit 6859057

Please sign in to comment.