diff --git a/src/cli/java/org/commcare/util/screen/MultiSelectEntityScreen.java b/src/cli/java/org/commcare/util/screen/MultiSelectEntityScreen.java index e7b774ab90..bce2b3cbcd 100644 --- a/src/cli/java/org/commcare/util/screen/MultiSelectEntityScreen.java +++ b/src/cli/java/org/commcare/util/screen/MultiSelectEntityScreen.java @@ -9,6 +9,7 @@ import org.commcare.core.interfaces.VirtualDataInstanceStorage; import org.commcare.data.xml.VirtualInstances; import org.commcare.modern.session.SessionWrapper; +import org.commcare.modern.util.Pair; import org.commcare.session.CommCareSession; import org.commcare.suite.model.MultiSelectEntityDatum; import org.commcare.util.FormDataUtil; @@ -80,7 +81,7 @@ public boolean autoSelectEntities(SessionWrapper session) { for (int i = 0; i < selectionSize; i++) { evaluatedValues[i] = getReturnValueFromSelection(references.elementAt(i)); } - processSelectionIntoInstance(evaluatedValues); + processSelectionIntoInstance(evaluatedValues, getNeededDatumId()); updateSession(session); return true; } @@ -134,9 +135,18 @@ private void prcessSelectionAsGuid(String guid) throws CommCareSessionException "Could not make selection with reference id " + guid + " on this screen. " + " If this error persists please report a bug to CommCareHQ."); } + validateEntitiesInInstance(cachedInstance); storageReferenceId = guid; } + private void validateEntitiesInInstance(ExternalDataInstance instance) throws CommCareSessionException { + AbstractTreeElement root = instance.getRoot(); + for (int i = 0; i < root.getNumChildren(); i++) { + String entityVal = root.getChildAt(i).getValue().uncast().getString(); + getAndValidateEntityReference(entityVal); + } + } + private String getNeededDatumId() { return getSession().getNeededDatum().getDataId(); } @@ -147,7 +157,7 @@ private void processSelectedReferences(TreeReference[] selectedRefs) { for (int i = 0; i < selectedRefs.length; i++) { evaluatedValues[i] = getReturnValueFromSelection(selectedRefs[i]); } - processSelectionIntoInstance(evaluatedValues); + processSelectionIntoInstance(evaluatedValues, getNeededDatumId()); } } @@ -156,29 +166,28 @@ private void processSelectedValues(String[] selectedValues) if (selectedValues != null && validateSelectionSize(selectedValues.length)) { String[] evaluatedValues = new String[selectedValues.length]; for (int i = 0; i < selectedValues.length; i++) { - TreeReference currentReference = getEntityReference(selectedValues[i]); - if (currentReference == null) { - throw new CommCareSessionException( - "Could not select case " + selectedValues[i] + " on this screen. " + - " If this error persists please report a bug to CommCareHQ."); - } + TreeReference currentReference = getAndValidateEntityReference(selectedValues[i]); evaluatedValues[i] = getReturnValueFromSelection(currentReference); } - processSelectionIntoInstance(evaluatedValues); + processSelectionIntoInstance(evaluatedValues, getNeededDatumId()); } } - private void processSelectionIntoInstance(String[] evaluatedValues) { - ExternalDataInstance instance = VirtualInstances.buildSelectedValuesInstance( - getSession().getNeededDatum().getDataId(), - evaluatedValues); - String guid = virtualDataInstanceStorage.write(instance); - storageReferenceId = guid; + private TreeReference getAndValidateEntityReference(String selectedValue) throws CommCareSessionException { + TreeReference currentReference = getEntityReference(selectedValue); + if (currentReference == null) { + throw new CommCareSessionException( + "Could not select case " + selectedValue + " on this screen. " + + " If this error persists please report a bug to CommCareHQ."); + } + return currentReference; + } - // rebuild instance with the source - ExternalDataInstanceSource instanceSource = ExternalDataInstanceSource.buildVirtual(instance, - storageReferenceId); - selectedValuesInstance = instanceSource.toInstance(); + private void processSelectionIntoInstance(String[] evaluatedValues, String instanceId) { + Pair guidAndInstance = VirtualInstances.storeSelectedValuesInInstance( + virtualDataInstanceStorage, evaluatedValues, instanceId); + storageReferenceId = guidAndInstance.first; + selectedValuesInstance = guidAndInstance.second; } @Override diff --git a/src/main/java/org/commcare/core/interfaces/RemoteInstanceFetcher.java b/src/main/java/org/commcare/core/interfaces/RemoteInstanceFetcher.java index ef62900a60..1c96c73673 100644 --- a/src/main/java/org/commcare/core/interfaces/RemoteInstanceFetcher.java +++ b/src/main/java/org/commcare/core/interfaces/RemoteInstanceFetcher.java @@ -12,6 +12,8 @@ public interface RemoteInstanceFetcher { AbstractTreeElement getExternalRoot(String instanceId, ExternalDataInstanceSource source, String refId) throws RemoteInstanceException; + VirtualDataInstanceStorage getVirtualDataInstanceStorage(); + class RemoteInstanceException extends Exception { public RemoteInstanceException(String message) { diff --git a/src/main/java/org/commcare/core/process/CommCareInstanceInitializer.java b/src/main/java/org/commcare/core/process/CommCareInstanceInitializer.java index 7c15d4644a..ffacb76cee 100644 --- a/src/main/java/org/commcare/core/process/CommCareInstanceInitializer.java +++ b/src/main/java/org/commcare/core/process/CommCareInstanceInitializer.java @@ -6,10 +6,12 @@ import org.commcare.cases.instance.CaseDataInstance; import org.commcare.cases.instance.CaseInstanceTreeElement; import org.commcare.cases.instance.LedgerInstanceTreeElement; +import org.commcare.core.interfaces.RemoteInstanceFetcher; import org.commcare.core.interfaces.UserSandbox; +import org.commcare.core.interfaces.VirtualDataInstanceStorage; import org.commcare.core.sandbox.SandboxUtils; import org.commcare.data.xml.VirtualInstances; -import org.commcare.session.CommCareSession; +import org.commcare.modern.session.SessionWrapper; import org.commcare.session.SessionFrame; import org.commcare.session.SessionInstanceBuilder; import org.commcare.suite.model.StackFrameStep; @@ -37,7 +39,7 @@ */ public class CommCareInstanceInitializer extends InstanceInitializationFactory { - protected final CommCareSession session; + protected final SessionWrapper sessionWrapper; protected CaseInstanceTreeElement casebase; protected LedgerInstanceTreeElement stockbase; private final LocalCacheTable fixtureBases = new LocalCacheTable<>(); @@ -54,16 +56,9 @@ public CommCareInstanceInitializer(UserSandbox sandbox) { this(null, sandbox, null); } - public CommCareInstanceInitializer(UserSandbox sandbox, CommCarePlatform platform) { - this(null, sandbox, platform); - } - - public CommCareInstanceInitializer(UserSandbox sandbox, CommCareSession session) { - this(session, sandbox, null); - } - - public CommCareInstanceInitializer(CommCareSession session, UserSandbox sandbox, CommCarePlatform platform) { - this.session = session; + public CommCareInstanceInitializer(SessionWrapper sessionWrapper, UserSandbox sandbox, + CommCarePlatform platform) { + this.sessionWrapper = sessionWrapper; this.mSandbox = sandbox; this.mPlatform = platform; } @@ -92,13 +87,44 @@ public InstanceRoot generateRoot(ExternalDataInstance instance) { } else if (ref.startsWith(ExternalDataInstance.JR_REMOTE_REFERENCE)) { return setupExternalDataInstance(instance, ref, SessionFrame.STATE_QUERY_REQUEST); } else if (ref.startsWith(JR_SELECTED_ENTITIES_REFERENCE)) { - return setupExternalDataInstance(instance, ref, SessionFrame.STATE_MULTIPLE_DATUM_VAL); + return setupSelectedEntitiesInstance(instance, ref); } else if (ref.startsWith(JR_SEARCH_INPUT_REFERENCE)) { return setupExternalDataInstance(instance, ref, SessionFrame.STATE_QUERY_REQUEST); } return ConcreteInstanceRoot.NULL; } + private InstanceRoot setupSelectedEntitiesInstance(ExternalDataInstance instance, String ref) { + String stepType = SessionFrame.STATE_MULTIPLE_DATUM_VAL; + InstanceRoot instanceRoot = setupExternalDataInstance(instance, ref, stepType); + if (instanceRoot == ConcreteInstanceRoot.NULL) { + instanceRoot = getExternalDataInstanceSourceByStepValue(instance, stepType); + } + return instanceRoot; + } + + // Tries to get instance by looking for the instance with id equal to the datum value in the storage + private InstanceRoot getExternalDataInstanceSourceByStepValue(ExternalDataInstance instance, + String stepType) { + RemoteInstanceFetcher instanceFetcher = sessionWrapper.getRemoteInstanceFetcher(); + if (instanceFetcher != null) { + VirtualDataInstanceStorage instanceStorage = instanceFetcher.getVirtualDataInstanceStorage(); + for (StackFrameStep step : sessionWrapper.getFrame().getSteps()) { + if (step.getType().equals(stepType)) { + try { + ExternalDataInstance loadedInstance = instanceStorage.read(step.getValue(), + instance.getInstanceId(), + instance.getReference()); + return new ConcreteInstanceRoot(loadedInstance.getRoot()); + } catch (VirtualInstances.InstanceNotFoundException e) { + // continue looping + } + } + } + } + return ConcreteInstanceRoot.NULL; + } + /** * Initialises instances with reference to 'selected_cases' * @@ -216,7 +242,7 @@ protected InstanceRoot setupSessionData(ExternalDataInstance instance) { } User u = mSandbox.getLoggedInUserUnsafe(); TreeElement root = - SessionInstanceBuilder.getSessionInstance(session.getFrame(), getDeviceId(), + SessionInstanceBuilder.getSessionInstance(sessionWrapper.getFrame(), getDeviceId(), getVersionString(), getCurrentDrift(), u.getUsername(), u.getUniqueId(), u.getProperties()); root.setParent(instance.getBase()); @@ -228,7 +254,7 @@ protected long getCurrentDrift() { } protected InstanceRoot getExternalDataInstanceSource(String reference, String stepType) { - for (StackFrameStep step : session.getFrame().getSteps()) { + for (StackFrameStep step : sessionWrapper.getFrame().getSteps()) { if (step.getType().equals(stepType) && step.hasDataInstanceSource(reference)) { return step.getDataInstanceSource(reference); } @@ -240,7 +266,7 @@ protected InstanceRoot getExternalDataInstanceSource(String reference, String st * Required for legacy instance support */ protected InstanceRoot getExternalDataInstanceSourceById(String instanceId, String stepType) { - for (StackFrameStep step : session.getFrame().getSteps()) { + for (StackFrameStep step : sessionWrapper.getFrame().getSteps()) { if (step.getType().equals(stepType)) { ExternalDataInstanceSource source = step.getDataInstanceSourceById(instanceId); if (source != null) { diff --git a/src/main/java/org/commcare/data/xml/VirtualInstances.java b/src/main/java/org/commcare/data/xml/VirtualInstances.java index 6427078d2e..661869a507 100644 --- a/src/main/java/org/commcare/data/xml/VirtualInstances.java +++ b/src/main/java/org/commcare/data/xml/VirtualInstances.java @@ -6,7 +6,10 @@ import com.google.common.collect.ImmutableMap; +import org.commcare.core.interfaces.VirtualDataInstanceStorage; +import org.commcare.modern.util.Pair; import org.javarosa.core.model.instance.ExternalDataInstance; +import org.javarosa.core.model.instance.ExternalDataInstanceSource; import org.javarosa.core.model.instance.TreeElement; import java.util.ArrayList; @@ -49,6 +52,27 @@ public static ExternalDataInstance buildSelectedValuesInstance( return new ExternalDataInstance(getSelectedEntitiesReference(instanceId), instanceId, root); } + /** + * Builds and stores the selected entitied into selected entities instance + * + * @param virtualDataInstanceStorage Instance Storage + * @param selectedValues Values to be stored into instance + * @param instanceId instance id for the new instance + * @return A pair of unique storage id for the instance and the newly generated instance + */ + public static Pair storeSelectedValuesInInstance( + VirtualDataInstanceStorage virtualDataInstanceStorage, String[] selectedValues, String instanceId) { + ExternalDataInstance instance = VirtualInstances.buildSelectedValuesInstance( + instanceId, + selectedValues); + String guid = virtualDataInstanceStorage.write(instance); + + // rebuild instance with the source + ExternalDataInstanceSource instanceSource = ExternalDataInstanceSource.buildVirtual(instance, guid); + ExternalDataInstance selectedValuesInstance = instanceSource.toInstance(); + return new Pair<>(guid, selectedValuesInstance); + } + public static String getSelectedEntitiesReference(String referenceId) { return getInstanceReference(JR_SELECTED_ENTITIES_REFERENCE, referenceId); @@ -92,4 +116,18 @@ public static String getReferenceScheme(String reference) { public static String getInstanceReference(String referenceScheme, String referenceId) { return referenceScheme.concat("/").concat(referenceId); } + + /** + * Throw when the data instance with the given key doesn't exist in the DB + */ + public static class InstanceNotFoundException extends RuntimeException { + + public InstanceNotFoundException(String key, String namespace) { + super(String.format( + "Could not find data instance with ID %s (namespace=%s)." + + "Redirecting to home screen. If this issue persists, please file a bug report.", + key, namespace + )); + } + } } diff --git a/src/main/java/org/commcare/modern/session/SessionWrapper.java b/src/main/java/org/commcare/modern/session/SessionWrapper.java index af6b0c3033..f04aaee86a 100644 --- a/src/main/java/org/commcare/modern/session/SessionWrapper.java +++ b/src/main/java/org/commcare/modern/session/SessionWrapper.java @@ -23,12 +23,19 @@ public class SessionWrapper extends CommCareSession implements SessionWrapperInt final protected UserSandbox mSandbox; final protected CommCarePlatform mPlatform; protected CommCareInstanceInitializer initializer; + protected RemoteInstanceFetcher remoteInstanceFetcher; - public SessionWrapper(CommCareSession session, CommCarePlatform platform, UserSandbox sandbox) { - this(platform, sandbox); + public SessionWrapper(CommCareSession session, CommCarePlatform platform, UserSandbox sandbox, + RemoteInstanceFetcher remoteInstanceFetcher) { + this(platform, sandbox, remoteInstanceFetcher); this.frame = session.getFrame(); this.setFrameStack(session.getFrameStack()); } + + + public SessionWrapper(CommCareSession session, CommCarePlatform platform, UserSandbox sandbox) { + this(session, platform, sandbox, null); + } public SessionWrapper(CommCarePlatform platform, UserSandbox sandbox) { super(platform); @@ -36,6 +43,13 @@ public SessionWrapper(CommCarePlatform platform, UserSandbox sandbox) { this.mPlatform = platform; } + public SessionWrapper(CommCarePlatform platform, UserSandbox sandbox, RemoteInstanceFetcher remoteInstanceFetcher) { + super(platform); + this.mSandbox = sandbox; + this.mPlatform = platform; + this.remoteInstanceFetcher = remoteInstanceFetcher; + } + /** * @return The evaluation context for the current state. */ @@ -72,7 +86,7 @@ public CommCareInstanceInitializer getIIF() { return initializer; } - public void prepareExternalSources(RemoteInstanceFetcher remoteInstanceFetcher) throws RemoteInstanceFetcher.RemoteInstanceException { + public void prepareExternalSources() throws RemoteInstanceFetcher.RemoteInstanceException { for(StackFrameStep step : frame.getSteps()) { step.initDataInstanceSources(remoteInstanceFetcher); } @@ -101,4 +115,8 @@ public String getNeededData() { public void stepBack() { super.stepBack(getEvaluationContext()); } + + public RemoteInstanceFetcher getRemoteInstanceFetcher() { + return remoteInstanceFetcher; + } } diff --git a/src/main/java/org/commcare/modern/session/SessionWrapperInterface.java b/src/main/java/org/commcare/modern/session/SessionWrapperInterface.java index dcf3cefe80..0ed9b1556d 100644 --- a/src/main/java/org/commcare/modern/session/SessionWrapperInterface.java +++ b/src/main/java/org/commcare/modern/session/SessionWrapperInterface.java @@ -22,5 +22,5 @@ public interface SessionWrapperInterface { EvaluationContext getRestrictedEvaluationContext(String commandId, Set instancesToInclude); EvaluationContext getEvaluationContextWithAccumulatedInstances(String commandID, XPathAnalyzable xPathAnalyzable); - void prepareExternalSources(RemoteInstanceFetcher fetcher) throws RemoteInstanceFetcher.RemoteInstanceException; + void prepareExternalSources() throws RemoteInstanceFetcher.RemoteInstanceException; } diff --git a/src/main/java/org/commcare/suite/model/Endpoint.java b/src/main/java/org/commcare/suite/model/Endpoint.java index be70e1fb0e..65e468bcc0 100644 --- a/src/main/java/org/commcare/suite/model/Endpoint.java +++ b/src/main/java/org/commcare/suite/model/Endpoint.java @@ -12,19 +12,20 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Set; import java.util.Vector; public class Endpoint implements Externalizable { String id; - Vector arguments; + Vector arguments; Vector stackOperations; // for serialization public Endpoint() { } - public Endpoint(String id, Vector arguments, Vector stackOperations) { + public Endpoint(String id, Vector arguments, Vector stackOperations) { this.id = id; this.arguments = arguments; this.stackOperations = stackOperations; @@ -33,7 +34,7 @@ public Endpoint(String id, Vector arguments, Vector stac @Override public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { id = ExtUtil.readString(in); - arguments = (Vector)ExtUtil.read(in, new ExtWrapList(String.class), pf); + arguments = (Vector)ExtUtil.read(in, new ExtWrapList(EndpointArgument.class), pf); stackOperations = (Vector)ExtUtil.read(in, new ExtWrapList(StackOperation.class), pf); } @@ -48,7 +49,7 @@ public String getId() { return id; } - public Vector getArguments() { + public Vector getArguments() { return arguments; } @@ -59,40 +60,60 @@ public Vector getStackOperations() { // Utility Functions public static void populateEndpointArgumentsToEvaluationContext(Endpoint endpoint, ArrayList args, EvaluationContext evaluationContext) { - Vector endpointArguments = endpoint.getArguments(); + Vector endpointArguments = endpoint.getArguments(); if (endpointArguments.size() > args.size()) { - Vector missingArguments = new Vector(endpointArguments.subList(args.size(), endpointArguments.size())); + Vector missingArguments = new Vector<>(); + for (int i = args.size(); i < endpointArguments.size(); i++) { + missingArguments.add(endpointArguments.get(i).getId()); + } throw new InvalidEndpointArgumentsException(missingArguments, null); } for (int i = 0; i < endpointArguments.size(); i++) { - String argumentName = endpointArguments.elementAt(i); + String argumentName = endpointArguments.elementAt(i).getId(); evaluationContext.setVariable(argumentName, args.get(i)); } } public static void populateEndpointArgumentsToEvaluationContext(Endpoint endpoint, HashMap args, EvaluationContext evaluationContext) { - Vector endpointArguments = endpoint.getArguments(); - - Vector missingArguments = (Vector)endpointArguments.clone(); - missingArguments.removeAll(args.keySet()); + Vector endpointArguments = endpoint.getArguments(); + Set argumentIds = args.keySet(); + Vector missingArguments = new Vector<>(); + for (EndpointArgument endpointArgument : endpointArguments) { + if(!argumentIds.contains(endpointArgument.getId())){ + missingArguments.add(endpointArgument.getId()); + } + } - Vector unexpectedArguments = new Vector(args.keySet()); - unexpectedArguments.removeAll(endpointArguments); + Vector unexpectedArguments = new Vector<>(); + for (String argumentId : argumentIds) { + if(!isValidArgumentId(endpointArguments, argumentId)){ + unexpectedArguments.add(argumentId); + } + } if (missingArguments.size() > 0 || unexpectedArguments.size() > 0) { throw new InvalidEndpointArgumentsException(missingArguments, unexpectedArguments); } for (int i = 0; i < endpointArguments.size(); i++) { - String argumentName = endpointArguments.elementAt(i); + String argumentName = endpointArguments.elementAt(i).getId(); if (args.containsKey(argumentName)) { evaluationContext.setVariable(argumentName, args.get(argumentName)); } } } + private static boolean isValidArgumentId(Vector endpointArguments, String argumentId) { + for (EndpointArgument endpointArgument : endpointArguments) { + if (endpointArgument.getId().contentEquals(argumentId)) { + return true; + } + } + return false; + } + public static class InvalidEndpointArgumentsException extends RuntimeException { private final Vector missingArguments; private final Vector unexpectedArguments; diff --git a/src/main/java/org/commcare/suite/model/EndpointArgument.java b/src/main/java/org/commcare/suite/model/EndpointArgument.java new file mode 100644 index 0000000000..8bbbe81d1b --- /dev/null +++ b/src/main/java/org/commcare/suite/model/EndpointArgument.java @@ -0,0 +1,74 @@ +package org.commcare.suite.model; + +import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.core.util.externalizable.ExtUtil; +import org.javarosa.core.util.externalizable.Externalizable; +import org.javarosa.core.util.externalizable.PrototypeFactory; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import javax.annotation.Nullable; + +/** + * Model class to represent an argument to Endpoint + */ +public class EndpointArgument implements Externalizable { + + private String id; + + @Nullable + private String instanceId; + + @Nullable + private String instanceSrc; + + // for serialization + public EndpointArgument() { + } + + public EndpointArgument(String id, @Nullable String instanceId, @Nullable String instanceSrc) { + this.id = id; + this.instanceId = instanceId; + this.instanceSrc = instanceSrc; + } + + @Override + public void readExternal(DataInputStream in, PrototypeFactory pf) + throws IOException, DeserializationException { + id = ExtUtil.readString(in); + instanceId = ExtUtil.nullIfEmpty(ExtUtil.readString(in)); + instanceSrc = ExtUtil.nullIfEmpty(ExtUtil.readString(in)); + } + + @Override + public void writeExternal(DataOutputStream out) throws IOException { + ExtUtil.writeString(out, id); + ExtUtil.writeString(out, ExtUtil.emptyIfNull(instanceId)); + ExtUtil.writeString(out, ExtUtil.emptyIfNull(instanceSrc)); + } + + public String getId() { + return id; + } + + @Nullable + public String getInstanceId() { + return instanceId; + } + + @Nullable + public String getInstanceSrc() { + return instanceSrc; + } + + /** + * If the argument should be processed into a external data instance + * + * @return true if the argument defines instance attributes, false otherwise + */ + public boolean isInstanceArgument() { + return instanceId != null; + } +} diff --git a/src/main/java/org/commcare/suite/model/StackFrameStep.java b/src/main/java/org/commcare/suite/model/StackFrameStep.java index 96573031bb..6258e2ffe5 100644 --- a/src/main/java/org/commcare/suite/model/StackFrameStep.java +++ b/src/main/java/org/commcare/suite/model/StackFrameStep.java @@ -197,6 +197,8 @@ public StackFrameStep defineStep(EvaluationContext ec, SessionDatum neededDatum) switch (elementType) { case SessionFrame.STATE_DATUM_VAL: return new StackFrameStep(SessionFrame.STATE_DATUM_VAL, id, evaluateValue(ec)); + case SessionFrame.STATE_MULTIPLE_DATUM_VAL: + return new StackFrameStep(SessionFrame.STATE_MULTIPLE_DATUM_VAL, id, evaluateValue(ec)); case SessionFrame.STATE_COMMAND_ID: return new StackFrameStep(SessionFrame.STATE_COMMAND_ID, evaluateValue(ec), null); case SessionFrame.STATE_UNKNOWN: diff --git a/src/main/java/org/commcare/xml/EndpointParser.java b/src/main/java/org/commcare/xml/EndpointParser.java index 428e720b3c..d5e3f0c9a1 100644 --- a/src/main/java/org/commcare/xml/EndpointParser.java +++ b/src/main/java/org/commcare/xml/EndpointParser.java @@ -1,6 +1,7 @@ package org.commcare.xml; import org.commcare.suite.model.Endpoint; +import org.commcare.suite.model.EndpointArgument; import org.commcare.suite.model.StackOperation; import org.javarosa.xml.ElementParser; import org.javarosa.xml.util.InvalidStructureException; @@ -12,11 +13,17 @@ import java.util.Vector; import static org.commcare.xml.StackOpParser.NAME_STACK; +import static org.javarosa.core.model.instance.ExternalDataInstance.JR_SELECTED_ENTITIES_REFERENCE; + +import com.google.common.collect.ImmutableList; public class EndpointParser extends ElementParser { static final String NAME_ENDPOINT = "endpoint"; private static final String NAME_ARGUMENT = "argument"; + private static final String ATTR_ARGUMENT_ID = "id"; + private static final String ATTR_ARGUMENT_INSTANCE_ID = "instance-id"; + private static final String ATTR_ARGUMENT_INSTANCE_SRC = "instance-src"; public EndpointParser(KXmlParser parser) { @@ -31,16 +38,30 @@ public Endpoint parse() throws InvalidStructureException, IOException, XmlPullPa } Vector stackOperations = new Vector<>(); - Vector arguments = new Vector<>(); + Vector arguments = new Vector<>(); while (nextTagInBlock(NAME_ENDPOINT)) { String tagName = parser.getName().toLowerCase(); if (tagName.contentEquals(NAME_ARGUMENT)) { - String argumentID = parser.getAttributeValue(null, "id"); + String argumentID = parser.getAttributeValue(null, ATTR_ARGUMENT_ID); if (argumentID == null || argumentID.isEmpty()) { throw new InvalidStructureException("argument must define a non empty id", parser); } - arguments.add(argumentID); + String argInstanceId = parser.getAttributeValue(null, ATTR_ARGUMENT_INSTANCE_ID); + String argInstanceSrc = parser.getAttributeValue(null, ATTR_ARGUMENT_INSTANCE_SRC); + + if (argInstanceId != null && argInstanceSrc == null) { + throw new InvalidStructureException( + "Endpoint argument containing a non-null instance-id must define an instance-src", parser); + } + + ImmutableList validInstanceSrc = ImmutableList.of(JR_SELECTED_ENTITIES_REFERENCE); + if (argInstanceSrc != null && !validInstanceSrc.contains(argInstanceSrc)) { + throw new InvalidStructureException( + "instance-src for an endpoint argument must be one of " + validInstanceSrc, parser); + } + + arguments.add(new EndpointArgument(argumentID, argInstanceId, argInstanceSrc)); } else if (tagName.contentEquals(NAME_STACK)) { StackOpParser sop = new StackOpParser(parser); while (this.nextTagInBlock(NAME_STACK)) { diff --git a/src/main/java/org/commcare/xml/StackFrameStepParser.java b/src/main/java/org/commcare/xml/StackFrameStepParser.java index dac8425428..2252c40a5e 100644 --- a/src/main/java/org/commcare/xml/StackFrameStepParser.java +++ b/src/main/java/org/commcare/xml/StackFrameStepParser.java @@ -26,10 +26,10 @@ class StackFrameStepParser extends ElementParser { public StackFrameStep parse() throws InvalidStructureException, IOException, XmlPullParserException { String operation = parser.getName(); String value = parser.getAttributeValue(null, "value"); + String datumId = parser.getAttributeValue(null, "id"); switch (operation) { case "datum": - String datumId = parser.getAttributeValue(null, "id"); return parseValue(SessionFrame.STATE_UNKNOWN, datumId); case "rewind": return parseValue(SessionFrame.STATE_REWIND, null); @@ -41,6 +41,8 @@ public StackFrameStep parse() throws InvalidStructureException, IOException, Xml return parseQuery(); case "jump": return parseJump(); + case "instance-datum": + return parseValue(SessionFrame.STATE_MULTIPLE_DATUM_VAL, datumId); default: throw new InvalidStructureException("<" + operation + "> is not a valid stack frame element!", this.parser); } diff --git a/src/test/resources/app_structure/suite.xml b/src/test/resources/app_structure/suite.xml index e104dd4df9..d35734161e 100644 --- a/src/test/resources/app_structure/suite.xml +++ b/src/test/resources/app_structure/suite.xml @@ -230,5 +230,13 @@ Menu - + + + + + + + + +