diff --git a/pom.xml b/pom.xml
index 17129eed..0ecc0d3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,9 @@
fr.insee
Pogues-BO
war
- 4.0.8
+
+ 4.1.0
+
Pogues-BO
@@ -33,7 +35,6 @@
jacoco
reuseReports
java
- ${project.basedir}/../target/jacoco.exec
-Xms256m -Xmx512m -XX:MaxPermSize=128m -ea -Dfile.encoding=UTF-8
@@ -175,6 +176,11 @@
org.projectlombok
lombok
+
+ org.eclipse.persistence
+ org.eclipse.persistence.moxy
+ 2.6.0
+
@@ -185,29 +191,25 @@
org.jacoco
jacoco-maven-plugin
${jacoco.version}
-
-
-
- pre-unit-test
-
- prepare-agent
-
-
-
- surefireArgLine
- true
-
-
-
- post-unit-test
- test
-
- report
-
-
-
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+
+ report
+
+
+
+ XML
+
+
+
+
org.sonarsource.scanner.maven
@@ -223,5 +225,4 @@
-
diff --git a/src/main/java/fr/insee/pogues/exception/DeReferencingException.java b/src/main/java/fr/insee/pogues/exception/DeReferencingException.java
new file mode 100644
index 00000000..b109d3bb
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/exception/DeReferencingException.java
@@ -0,0 +1,12 @@
+package fr.insee.pogues.exception;
+
+/**
+ * Exception thrown if an error occurs during questionnaire de-referencing (composition feature).
+ */
+public class DeReferencingException extends Exception {
+
+ public DeReferencingException(String message, Exception e) {
+ super(message, e);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/exception/IllegalFlowControlException.java b/src/main/java/fr/insee/pogues/exception/IllegalFlowControlException.java
new file mode 100644
index 00000000..dcb973fc
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/exception/IllegalFlowControlException.java
@@ -0,0 +1,9 @@
+package fr.insee.pogues.exception;
+
+public class IllegalFlowControlException extends Exception {
+
+ public IllegalFlowControlException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/exception/IllegalIterationException.java b/src/main/java/fr/insee/pogues/exception/IllegalIterationException.java
new file mode 100644
index 00000000..c0200f41
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/exception/IllegalIterationException.java
@@ -0,0 +1,9 @@
+package fr.insee.pogues.exception;
+
+public class IllegalIterationException extends Exception {
+
+ public IllegalIterationException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/exception/NullReferenceException.java b/src/main/java/fr/insee/pogues/exception/NullReferenceException.java
new file mode 100644
index 00000000..12ab7ed1
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/exception/NullReferenceException.java
@@ -0,0 +1,10 @@
+package fr.insee.pogues.exception;
+
+/** Thrown when a referenced questionnaire is null when doing questionnaire de-referencing. */
+public class NullReferenceException extends Exception {
+
+ public NullReferenceException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/exception/PoguesDeserializationException.java b/src/main/java/fr/insee/pogues/exception/PoguesDeserializationException.java
new file mode 100644
index 00000000..21cdf23e
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/exception/PoguesDeserializationException.java
@@ -0,0 +1,13 @@
+package fr.insee.pogues.exception;
+
+public class PoguesDeserializationException extends Exception {
+
+ public PoguesDeserializationException(String message, Exception e) {
+ super(message, e);
+ }
+
+ public PoguesDeserializationException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/persistence/service/VariablesService.java b/src/main/java/fr/insee/pogues/persistence/service/VariablesService.java
new file mode 100644
index 00000000..bf1726bf
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/persistence/service/VariablesService.java
@@ -0,0 +1,22 @@
+package fr.insee.pogues.persistence.service;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+public interface VariablesService {
+
+ /**
+ * Used for pogues frontend
+ * @param id questionnaire id
+ * @return variables as json string with caveats from pogues-model (like format for datedatatype, ...)
+ */
+ String getVariablesByQuestionnaire(String id);
+
+ /**
+ * Used for public enemy, delivers
+ * @param id
+ * @return variables as json directly from DB
+ */
+ JSONArray getVariablesByQuestionnaireForPublicEnemy(String id);
+
+}
diff --git a/src/main/java/fr/insee/pogues/persistence/service/VariablesServiceImpl.java b/src/main/java/fr/insee/pogues/persistence/service/VariablesServiceImpl.java
new file mode 100644
index 00000000..7c182295
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/persistence/service/VariablesServiceImpl.java
@@ -0,0 +1,97 @@
+package fr.insee.pogues.persistence.service;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.stream.StreamSource;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.persistence.jaxb.MarshallerProperties;
+import org.eclipse.persistence.jaxb.UnmarshallerProperties;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import fr.insee.pogues.model.Questionnaire;
+import fr.insee.pogues.persistence.query.QuestionnairesServiceQuery;
+
+@Service
+@Slf4j
+public class VariablesServiceImpl implements VariablesService {
+
+ private static final Logger logger = LogManager.getLogger(VariablesServiceImpl.class);
+
+ @Autowired
+ JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ private QuestionnairesServiceQuery questionnaireServiceQuery;
+
+ public VariablesServiceImpl() {}
+
+ public VariablesServiceImpl(QuestionnairesServiceQuery questionnairesServiceQuery) {
+ this.questionnaireServiceQuery = questionnairesServiceQuery;
+ }
+
+ public JSONArray getVariablesByQuestionnaireForPublicEnemy(String id){
+ try {
+ JSONObject questionnaire = questionnaireServiceQuery.getQuestionnaireByID(id);
+ // We test the existence of the questionnaire in repository
+ if (questionnaire != null) {
+ JSONObject variables = (JSONObject) questionnaire.get("Variables");
+ return (JSONArray) variables.get("Variable");
+ }
+ } catch (Exception e) {
+ log.error("Exception occurred when trying to get variables from questionnaire with id={}", id, e);
+ }
+ return null;
+ }
+
+ public String getVariablesByQuestionnaire(String id){
+ StreamSource json = null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ JSONObject questionnaire = questionnaireServiceQuery.getQuestionnaireByID(id);
+ // We test the existence of the questionnaire in repository
+ if (questionnaire != null) {
+ logger.info("Deserializing questionnaire ");
+ JAXBContext context = JAXBContext.newInstance(Questionnaire.class);
+ Unmarshaller unmarshaller = context.createUnmarshaller();
+ unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
+ unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
+ try(InputStream inQuestionnaire = new ByteArrayInputStream(questionnaire.toString().getBytes())){
+ json = new StreamSource(inQuestionnaire);
+ Questionnaire questionnaireJava = unmarshaller.unmarshal(json, Questionnaire.class).getValue();
+ logger.info("Questionnaire " + questionnaireJava.getId() + " successfully deserialized");
+ logger.info("Serializing variables for questionnaire {}", questionnaireJava.getId());
+ JAXBContext context2 = JAXBContext.newInstance(Questionnaire.Variables.class);
+ Marshaller marshaller = context2.createMarshaller();
+ marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
+ // Set it to true if you need to include the JSON root element in the JSON output
+ marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
+ // Set it to true if you need the JSON output to formatted
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ // Marshal the questionnaire object to JSON and put the output in a string
+ marshaller.marshal(questionnaireJava.getVariables(), baos);
+ }
+ return baos.toString(StandardCharsets.UTF_8);
+ }
+ } catch (Exception e) {
+ log.error("Exception occurred when trying to get variables from questionnaire with id={}", id, e);
+ } finally {
+ IOUtils.closeQuietly(baos);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDeref.java b/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDeref.java
new file mode 100644
index 00000000..a1eccbbd
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDeref.java
@@ -0,0 +1,6 @@
+package fr.insee.pogues.transforms.visualize;
+
+import fr.insee.pogues.transforms.Transformer;
+
+public interface PoguesJSONToPoguesJSONDeref extends Transformer {
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDerefImpl.java b/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDerefImpl.java
new file mode 100644
index 00000000..c1d82e6a
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/PoguesJSONToPoguesJSONDerefImpl.java
@@ -0,0 +1,115 @@
+package fr.insee.pogues.transforms.visualize;
+
+import fr.insee.pogues.exception.NullReferenceException;
+import fr.insee.pogues.model.Questionnaire;
+import fr.insee.pogues.persistence.service.QuestionnairesService;
+import fr.insee.pogues.utils.PoguesDeserializer;
+import fr.insee.pogues.utils.PoguesSerializer;
+import fr.insee.pogues.transforms.visualize.composition.QuestionnaireComposition;
+import fr.insee.pogues.utils.json.JSONFunctions;
+import org.apache.commons.io.IOUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class PoguesJSONToPoguesJSONDerefImpl implements PoguesJSONToPoguesJSONDeref{
+
+ static final Logger logger = LogManager.getLogger(PoguesJSONToPoguesJSONDerefImpl.class);
+
+ private static final String NULL_INPUT_MESSAGE = "Null input";
+ private static final String NULL_OUTPUT_MESSAGE = "Null output";
+
+ @Autowired
+ QuestionnairesService questionnairesService;
+
+ public PoguesJSONToPoguesJSONDerefImpl() {}
+
+ public PoguesJSONToPoguesJSONDerefImpl(QuestionnairesService questionnairesService) {
+ this.questionnairesService = questionnairesService;
+ }
+
+ @Override
+ public void transform(InputStream input, OutputStream output, Map params, String surveyName) throws Exception {
+ if (null == input) {
+ throw new NullPointerException(NULL_INPUT_MESSAGE);
+ }
+ if (null == output) {
+ throw new NullPointerException(NULL_OUTPUT_MESSAGE);
+ }
+ String jsonDeref = transform(input, params, surveyName);
+ output.write(jsonDeref.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public String transform(InputStream input, Map params, String surveyName) throws Exception {
+ if (null == input) {
+ throw new NullPointerException(NULL_INPUT_MESSAGE);
+ }
+ return transform(IOUtils.toString(input, StandardCharsets.UTF_8), params, surveyName);
+ }
+
+ @Override
+ public String transform(String input, Map params, String surveyName) throws Exception {
+ if (null == input) {
+ throw new NullPointerException(NULL_INPUT_MESSAGE);
+ }
+ // TODO: This parameter could be replaced by logical check in back-office
+ // (when Pogues-Model supports "childQuestionnaireRef")
+ if (!(boolean) params.get("needDeref")) {
+ logger.info("No de-referencing needed");
+ return input;
+ }
+ Questionnaire questionnaire = transformAsQuestionnaire(input);
+ return PoguesSerializer.questionnaireJavaToString(questionnaire);
+ }
+
+ public Questionnaire transformAsQuestionnaire(String input) throws Exception {
+ if (null == input) {
+ throw new NullPointerException(NULL_INPUT_MESSAGE);
+ }
+ // Parse Pogues json questionnaire
+ JSONParser parser = new JSONParser();
+ JSONObject jsonQuestionnaire = (JSONObject) parser.parse(input);
+ // Get referenced questionnaire identifiers
+ // TODO: The "childQuestionnaireRef" in the json should be supported by Pogues-Model
+ List references = JSONFunctions.getChildReferencesFromQuestionnaire(jsonQuestionnaire);
+ // Deserialize json into questionnaire object
+ Questionnaire questionnaire = PoguesDeserializer.questionnaireToJavaObject(jsonQuestionnaire);
+ //
+ deReference(references, questionnaire);
+ logger.info("Sequences inserted");
+ //
+ return questionnaire;
+ }
+
+ private void deReference(List references, Questionnaire questionnaire) throws Exception {
+ for (String reference : references) {
+ JSONObject referencedJsonQuestionnaire = questionnairesService.getQuestionnaireByID(reference);
+ if (referencedJsonQuestionnaire == null) {
+ throw new NullReferenceException(String.format(
+ "Null reference behind reference '%s' in questionnaire '%s'.",
+ reference, questionnaire.getId()));
+ } else {
+ Questionnaire referencedQuestionnaire = PoguesDeserializer.questionnaireToJavaObject(referencedJsonQuestionnaire);
+ // Coherence check
+ if (! reference.equals(referencedQuestionnaire.getId())) {
+ logger.warn("Reference '{}' found in questionnaire '{}' mismatch referenced questionnaire's id '{}'",
+ reference, questionnaire.getId(), referencedQuestionnaire.getId());
+ }
+ //
+ QuestionnaireComposition.insertReference(questionnaire, referencedQuestionnaire);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/CompositionStep.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/CompositionStep.java
new file mode 100644
index 00000000..8be1f25c
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/CompositionStep.java
@@ -0,0 +1,19 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.exception.DeReferencingException;
+import fr.insee.pogues.model.Questionnaire;
+
+/**
+ * Interface for processing step when de-referencing a questionnaire.
+ */
+public interface CompositionStep {
+
+ /**
+ * Update questionnaire content with referenced questionnaire given.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @throws DeReferencingException if an error occurs during the de-referencing step.
+ */
+ void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire) throws DeReferencingException;
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertCodeLists.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertCodeLists.java
new file mode 100644
index 00000000..6855f23a
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertCodeLists.java
@@ -0,0 +1,62 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.model.CodeLists;
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Implementation of CompositionStep to insert code lists of a referenced questionnaire.
+ */
+@Slf4j
+class InsertCodeLists implements CompositionStep {
+
+ /** Host questionnaire. */
+ private Questionnaire questionnaire;
+ /** Host questionnaire code list labels. */
+ private final Set codeListLabels = new HashSet<>();
+
+ /**
+ * Insert code lists of the referenced questionnaire in the referencing questionnaire.
+ * If a code list of the referenced questionnaire has the same label as a list in the referencing questionnaire,
+ * the code list is not added.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire) {
+ //
+ this.questionnaire = questionnaire;
+ //
+ CodeLists refCodeLists = referencedQuestionnaire.getCodeLists();
+ if (refCodeLists == null) {
+ log.info("No code lists in referenced questionnaire '{}'", referencedQuestionnaire.getId());
+ return;
+ }
+ //
+ hostCodeLists();
+ //
+ if (questionnaire.getCodeLists() == null)
+ questionnaire.setCodeLists(new CodeLists());
+ //
+ refCodeLists.getCodeList().forEach(codeList -> {
+ if (codeListLabels.contains(codeList.getLabel())) {
+ log.info("Code list with label '{}' is already in host questionnaire '{}', " +
+ "so it has not been inserted from reference '{}'",
+ codeList.getLabel(), questionnaire.getId(), referencedQuestionnaire.getId());
+ return;
+ }
+ questionnaire.getCodeLists().getCodeList().add(codeList);
+
+ });
+ log.info("Code lists from '{}' inserted in '{}'", referencedQuestionnaire.getId(), questionnaire.getId());
+ }
+
+ private void hostCodeLists() {
+ if (questionnaire.getCodeLists() != null)
+ questionnaire.getCodeLists().getCodeList().forEach(codeList -> codeListLabels.add(codeList.getLabel()));
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertFlowControls.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertFlowControls.java
new file mode 100644
index 00000000..bb57622e
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertFlowControls.java
@@ -0,0 +1,23 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Implementation of CompositionStep to insert flow controls of a referenced questionnaire.
+ */
+@Slf4j
+class InsertFlowControls implements CompositionStep {
+
+ /**
+ * Insert flow controls of the referenced questionnaire in the referencing questionnaire.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire) {
+ questionnaire.getFlowControl().addAll(referencedQuestionnaire.getFlowControl());
+ log.info("FlowControl from '{}' inserted in '{}'", referencedQuestionnaire.getId(), questionnaire.getId());
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertIterations.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertIterations.java
new file mode 100644
index 00000000..84277a6f
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertIterations.java
@@ -0,0 +1,31 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Implementation of CompositionStep to insert iterations of a referenced questionnaire.
+ */
+@Slf4j
+class InsertIterations implements CompositionStep {
+
+ /**
+ * Insert iterations of the referenced questionnaire in the referencing questionnaire.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire) {
+ Questionnaire.Iterations refIterations = referencedQuestionnaire.getIterations();
+ if (refIterations == null) {
+ log.info("No iterations in referenced questionnaire '{}'", referencedQuestionnaire.getId());
+ return;
+ }
+ if (questionnaire.getIterations() == null)
+ questionnaire.setIterations(new Questionnaire.Iterations());
+ questionnaire.getIterations().getIteration().addAll(refIterations.getIteration());
+ log.info("Iterations from '{}' inserted in '{}'", referencedQuestionnaire.getId(), questionnaire.getId());
+
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertSequences.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertSequences.java
new file mode 100644
index 00000000..ab68176e
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/InsertSequences.java
@@ -0,0 +1,45 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.exception.DeReferencingException;
+import fr.insee.pogues.model.ComponentType;
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+import static fr.insee.pogues.utils.PoguesModelUtils.getSequences;
+
+/**
+ * Implementation of CompositionStep to replace questionnaire reference by its sequences.
+ */
+@Slf4j
+class InsertSequences implements CompositionStep {
+
+ /**
+ * Replace questionnaire reference by its sequences.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire) {
+ //
+ List refSequences = getSequences(referencedQuestionnaire);
+ int indexOfModification = 0;
+ for (ComponentType seq : questionnaire.getChild()) {
+ if (seq.getId().equals(referencedQuestionnaire.getId())) {
+ break;
+ }
+ indexOfModification++;
+ }
+ log.debug("Index to modify {}", indexOfModification);
+ // Suppression of the questionnaire reference
+ questionnaire.getChild().remove(indexOfModification);
+ // Insertion of the sequences
+ for (int i=0; i referenceSequences = getSequences(referencedQuestionnaire);
+ endMember = referenceSequences.get(referenceSequences.size() - 1).getId();
+ }
+ flowControlType.setIfTrue(beginMember+"-"+endMember);
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateIterationBounds.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateIterationBounds.java
new file mode 100644
index 00000000..b9f2f4d3
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateIterationBounds.java
@@ -0,0 +1,70 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.exception.DeReferencingException;
+import fr.insee.pogues.exception.IllegalIterationException;
+import fr.insee.pogues.model.ComponentType;
+import fr.insee.pogues.model.IterationType;
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+import static fr.insee.pogues.utils.PoguesModelUtils.getIterationBounds;
+import static fr.insee.pogues.utils.PoguesModelUtils.getSequences;
+
+/**
+ * Implementation of CompositionStep to update Iteration (loops) objects when de-referencing a questionnaire.
+ */
+@Slf4j
+class UpdateIterationBounds implements CompositionStep {
+
+ /**
+ * Update iterations of the referencing questionnaire: if a start/end member of an iteration is a referenced
+ * questionnaire, replace the reference id by the right element's id from the referenced questionnaire.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @throws DeReferencingException if an error occurs during iterations update.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire)
+ throws DeReferencingException {
+ if (questionnaire.getIterations() != null) {
+ try {
+ for (IterationType iterationType : questionnaire.getIterations().getIteration()) {
+ updateIterationBounds(referencedQuestionnaire, iterationType);
+ }
+ log.info("Iterations' bounds updated in '{}' when de-referencing '{}'",
+ questionnaire.getId(), referencedQuestionnaire.getId());
+ } catch (IllegalIterationException e) {
+ String message = String.format(
+ "Error when updating iteration bounds in questionnaire '%s' with reference '%s'",
+ questionnaire.getId(), referencedQuestionnaire.getId());
+ throw new DeReferencingException(message, e);
+ }
+ }
+ }
+
+ /** Replace loop bounds that reference a questionnaire by its first or last sequence.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @param iterationType The Iteration object to be updated.
+ * @throws IllegalIterationException if the 'MemberReference' property in the iteration is invalid.
+ */
+ static void updateIterationBounds(Questionnaire referencedQuestionnaire, IterationType iterationType)
+ throws IllegalIterationException {
+ //
+ String reference = referencedQuestionnaire.getId();
+ //
+ List iterationBounds = getIterationBounds(iterationType);
+ // Replace questionnaire reference by its first/last sequence
+ String beginMember = iterationBounds.get(0);
+ String endMember = iterationBounds.get(1);
+ if (beginMember.equals(reference)) {
+ iterationBounds.set(0, referencedQuestionnaire.getChild().get(0).getId());
+ }
+ if (endMember.equals(reference)) {
+ List referenceSequences = getSequences(referencedQuestionnaire);
+ iterationBounds.set(1, referenceSequences.get(referenceSequences.size() - 1).getId());
+ }
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateReferencedVariablesScope.java b/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateReferencedVariablesScope.java
new file mode 100644
index 00000000..9dc434d0
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/transforms/visualize/composition/UpdateReferencedVariablesScope.java
@@ -0,0 +1,125 @@
+package fr.insee.pogues.transforms.visualize.composition;
+
+import fr.insee.pogues.exception.DeReferencingException;
+import fr.insee.pogues.exception.IllegalIterationException;
+import fr.insee.pogues.model.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+import static fr.insee.pogues.utils.PoguesModelUtils.getIterationBounds;
+
+/**
+ * Implementation of CompositionStep to update variable scopes when de-referencing a questionnaire.
+ */
+@Slf4j
+class UpdateReferencedVariablesScope implements CompositionStep {
+
+ /**
+ * If the referenced questionnaire is in an iteration (loop) in referencing questionnaire,
+ * variables in referenced questionnaire that have null scope have to be updated.
+ * Warning: This must be done BEFORE replacing referenced questionnaire by its content.
+ * (Otherwise, we would have to scan every iteration and determine the variables in their scope,
+ * which would be much more complex.)
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @throws DeReferencingException if an error occurs during variable scopes update.
+ */
+ @Override
+ public void apply(Questionnaire questionnaire, Questionnaire referencedQuestionnaire)
+ throws DeReferencingException {
+ try {
+ if (questionnaire.getIterations() != null)
+ updateReferencedVariablesScope(questionnaire, referencedQuestionnaire);
+ } catch (IllegalIterationException e) {
+ String message = String.format(
+ "Error when updating referenced variables scope in questionnaire '%s' with reference '%s'",
+ questionnaire.getId(), referencedQuestionnaire.getId());
+ throw new DeReferencingException(message, e);
+ }
+ }
+
+ /**
+ * Iterate on iterations of the referencing questionnaire.
+ * For each iteration: update variables scope if the referenced questionnaire is in the scope of the iteration.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @throws IllegalIterationException If the 'MemberReference' property is not of size 2
+ * in one on the iteration object that has been scanned by the method.
+ */
+ static void updateReferencedVariablesScope(Questionnaire questionnaire, Questionnaire referencedQuestionnaire)
+ throws IllegalIterationException {
+ for (IterationType iterationType : questionnaire.getIterations().getIteration()) {
+ String scope = updateReferenceIfInBounds(questionnaire, referencedQuestionnaire, iterationType);
+ if (scope != null) {
+ log.info("Scope of root variables from referenced questionnaire '{}' set to iteration scope '{}'",
+ referencedQuestionnaire.getId(), scope);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Scan the referencing questionnaire to determine if the referenced questionnaire is in the scope of the
+ * loop (Iteration) given. If so, update the scope of the referenced questionnaire's variables.
+ * @param questionnaire Referencing questionnaire.
+ * @param referencedQuestionnaire Referenced questionnaire.
+ * @param iterationType An iteration (loop) object.
+ * @return null if the referenced questionnaire is not in the scope of the iteration given.
+ * Otherwise, the identifier of the iteration, that has the referenced questionnaire in its scope.
+ * @throws IllegalIterationException If the 'MemberReference' property is not of size 2
+ * in one on the iteration object that has been scanned by the method.
+ */
+ private static String updateReferenceIfInBounds(Questionnaire questionnaire,
+ Questionnaire referencedQuestionnaire,
+ IterationType iterationType) throws IllegalIterationException {
+ List iterationBounds = getIterationBounds(iterationType);
+ String beginMember = iterationBounds.get(0);
+ String endMember = iterationBounds.get(1);
+ //
+ String result = null;
+ boolean inScope = false;
+ // Iterate on first level (sequences / questionnaire references)
+ for (ComponentType component : questionnaire.getChild()) {
+ // Declare following components as in the iteration's scope
+ if (beginMember.equals(component.getId())) {
+ inScope = true;
+ }
+ // If the referenced questionnaire is found...
+ if (referencedQuestionnaire.getId().equals(component.getId())) {
+ // If it is in iteration's scope, then update its variables
+ if (inScope) {
+ result = iterationType.getId();
+ updateVariablesScope(referencedQuestionnaire, iterationType.getId());
+ }
+ // We have found what we wanted for this iteration object, break
+ break;
+ }
+ // If end of the iteration's scope is reached, break
+ if (endMember.equals(component.getId())) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Update the scope of variables that have a null scope in given questionnaire with iteration id given
+ * (variables that have a non-null scope are unchanged, see model's documentation).
+ * Only calculated and external variables are updated
+ * (collected variables in Pogues' model should not have a scope, see model's documentation).
+ * @param referencedQuestionnaire Questionnaire object.
+ * @param iterationId Identifier of the iteration that will be the scope of null-scope variables.
+ */
+ private static void updateVariablesScope(Questionnaire referencedQuestionnaire, String iterationId) {
+ referencedQuestionnaire.getVariables().getVariable().stream()
+ .filter(variableType -> variableType instanceof CalculatedVariableType
+ || variableType instanceof ExternalVariableType)
+ .forEach(variableType -> {
+ if (variableType.getScope() == null) {
+ variableType.setScope(iterationId);
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/utils/PoguesDeserializer.java b/src/main/java/fr/insee/pogues/utils/PoguesDeserializer.java
new file mode 100644
index 00000000..265ab69f
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/utils/PoguesDeserializer.java
@@ -0,0 +1,64 @@
+package fr.insee.pogues.utils;
+
+import fr.insee.pogues.exception.PoguesDeserializationException;
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.persistence.jaxb.UnmarshallerProperties;
+import org.json.simple.JSONObject;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.stream.StreamSource;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** This should be moved in Pogues-Model. */
+@Slf4j
+public class PoguesDeserializer {
+
+ private PoguesDeserializer() {}
+
+ /**
+ * Converts the json object questionnaire given in a Pogues-Model questionnaire object.
+ * @param jsonQuestionnaire Json object representing a Pogues questionnaire.
+ * @return Corresponding Pogues-Model questionnaire object.
+ * @throws PoguesDeserializationException if deserialization fails.
+ */
+ public static Questionnaire questionnaireToJavaObject(JSONObject jsonQuestionnaire)
+ throws PoguesDeserializationException {
+ log.info("Deserializing json questionnaire");
+ String questionnaireId = getIdFromJson(jsonQuestionnaire);
+ try (InputStream inQuestionnaire = new ByteArrayInputStream(jsonQuestionnaire.toString().getBytes())) {
+ StreamSource json = new StreamSource(inQuestionnaire);
+ //
+ JAXBContext context = JAXBContext.newInstance(Questionnaire.class);
+ Unmarshaller unmarshaller = context.createUnmarshaller();
+ unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
+ unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
+ //
+ Questionnaire questionnaire = unmarshaller.unmarshal(json, Questionnaire.class).getValue();
+ log.info("Successfully deserialized json questionnaire '{}'", questionnaireId);
+ return questionnaire;
+ } catch (IOException | JAXBException e) {
+ throw new PoguesDeserializationException(
+ "Exception occurred while trying to deserialize json questionnaire '"+questionnaireId+"'",
+ e);
+ }
+ }
+
+ private static String getIdFromJson(JSONObject jsonQuestionnaire) throws PoguesDeserializationException {
+ try {
+ String id = (String) jsonQuestionnaire.get("id");
+ if (id == null) {
+ throw new PoguesDeserializationException("Property 'id' is null in given json questionnaire.");
+ }
+ return id;
+ } catch (Exception e) {
+ throw new PoguesDeserializationException(
+ "Unable to retrieve 'id' property in given json questionnaire", e);
+ }
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/utils/PoguesModelUtils.java b/src/main/java/fr/insee/pogues/utils/PoguesModelUtils.java
new file mode 100644
index 00000000..74b89377
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/utils/PoguesModelUtils.java
@@ -0,0 +1,79 @@
+package fr.insee.pogues.utils;
+
+import fr.insee.pogues.exception.IllegalFlowControlException;
+import fr.insee.pogues.exception.IllegalIterationException;
+import fr.insee.pogues.model.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Helper class to factorize methods on Pogues-Model objects.
+ * Some parts of the model should be revised to make this class obsolete. */
+@Slf4j
+public class PoguesModelUtils {
+
+ /** Name of the artificial end sequence added by the front (to manage some GoTo cases). */
+ public static final String FAKE_LAST_ELEMENT_ID = "idendquest";
+
+ private PoguesModelUtils() {}
+
+ /**
+ * Return the list of components of depth 1 (that are sequences and/or questionnaire references)
+ * of the given questionnaire, without the fake last sequence component (filtered using its specific id).
+ * @param questionnaire A Questionnaire object.
+ * @return A list of sequences / questionnaire references, in the order defined in the questionnaire.
+ */
+ public static List getSequences(Questionnaire questionnaire) {
+ return questionnaire.getChild().stream()
+ .filter(componentType -> !FAKE_LAST_ELEMENT_ID.equals(componentType.getId()))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * The 'IfTrue' property defines begin/end member references (separated with '-') of the filter.
+ * @param flowControlType A FlowControl object.
+ * @return A String array of size 2 containing begin/end member references of the filter.
+ * @throws IllegalFlowControlException If the FlowControl 'IfTrue' property doesn't match the format "id-id".
+ */
+ public static String[] getFlowControlBounds(FlowControlType flowControlType) throws IllegalFlowControlException {
+ if (flowControlType.getIfTrue() == null) {
+ throw new IllegalFlowControlException(String.format(
+ "'IfTrue' property is null in FlowControl '%s'",
+ flowControlType.getId()));
+ }
+ String[] flowControlBounds = flowControlType.getIfTrue().split("-");
+ if (flowControlBounds.length != 2) {
+ throw new IllegalFlowControlException(String.format(
+ "'IfTrue' value '%s' is not compliant with Pogues-Model specification in FlowControl '%s'",
+ flowControlType.getIfTrue(), flowControlType.getId()));
+ }
+ return flowControlBounds;
+ }
+
+ /**
+ * The 'MemberReference' property is a list containing begin/end member references of the loop.
+ * If the list contains only one reference, it means that begin and end members are the same.
+ * A 'MemberReference' property with one element is accepted for now, yet a warning is shown in the log in that case
+ * (Pogues UI will be updated so that this case should not exist).
+ * @param iterationType An Iteration object.
+ * @return A List of strings of size 2 containing begin/end member references of the iteration.
+ * (The result will always be of size 2 even if begin and end members are equal.)
+ * @throws IllegalIterationException If The 'MemberReference' property is not of size 1 or 2.
+ */
+ public static List getIterationBounds(IterationType iterationType) throws IllegalIterationException {
+ int size = iterationType.getMemberReference().size();
+ if (!(size == 2 || size == 1)) {
+ throw new IllegalIterationException(String.format(
+ "'MemberReference' of iteration object '%s' contains %s references (should contain 1 or 2).",
+ iterationType.getId(), size));
+ }
+ if (size == 1) {
+ log.warn("'MemberReference' property with 1 element is deprecated (iteration object '{}').",
+ iterationType.getId());
+ iterationType.getMemberReference().add(iterationType.getMemberReference().get(0));
+ }
+ return iterationType.getMemberReference();
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/utils/PoguesSerializer.java b/src/main/java/fr/insee/pogues/utils/PoguesSerializer.java
new file mode 100644
index 00000000..3e7f8631
--- /dev/null
+++ b/src/main/java/fr/insee/pogues/utils/PoguesSerializer.java
@@ -0,0 +1,43 @@
+package fr.insee.pogues.utils;
+
+import fr.insee.pogues.model.Questionnaire;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.persistence.jaxb.MarshallerProperties;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/** This should be moved in Pogues-Model. */
+@Slf4j
+public class PoguesSerializer {
+
+ private PoguesSerializer() {}
+
+ /**
+ * Convert the given questionnaire object in a json string.
+ * @param questionnaire Pogues-Model questionnaire.
+ * @return Questionnaire as json string.
+ */
+ public static String questionnaireJavaToString(Questionnaire questionnaire) {
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ JAXBContext context = JAXBContext.newInstance(Questionnaire.class);
+ Marshaller marshaller = context.createMarshaller();
+ marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
+ // Set it to true if you need to include the JSON root element in the JSON output
+ marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
+ // Set it to true if you need the JSON output to formatted
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ // Marshal the questionnaire object to JSON and put the output in a string
+ marshaller.marshal(questionnaire, outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ } catch (JAXBException | IOException e) {
+ log.error("Unable to serialize Pogues questionnaire '{}'.", questionnaire, e);
+ return "";
+ }
+ }
+
+}
diff --git a/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java b/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java
index bf7293a5..94ec9589 100644
--- a/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java
+++ b/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java
@@ -12,6 +12,8 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* This class contains JSON functions to convert Java collection on JSON string.
@@ -137,6 +139,14 @@ public static List getQuestionnaireIDinQuestionnaireList(String question
return result;
}
+
+ public static List getChildReferencesFromQuestionnaire(JSONObject questionnaire) {
+ JSONArray references = (JSONArray) questionnaire.get("childQuestionnaireRef");
+ return IntStream.range(0, references.size())
+ .mapToObj(references::get)
+ .map(Object::toString)
+ .collect(Collectors.toList());
+ }
diff --git a/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java b/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java
index 89cabc3a..54746825 100644
--- a/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java
+++ b/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java
@@ -1,5 +1,15 @@
package fr.insee.pogues.webservice.rest;
+import fr.insee.pogues.config.auth.UserProvider;
+import fr.insee.pogues.config.auth.user.User;
+import fr.insee.pogues.persistence.service.QuestionnairesService;
+import fr.insee.pogues.persistence.service.VariablesService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.ArrayList;
import java.util.List;
@@ -9,6 +19,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
@@ -17,14 +28,11 @@
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
-import fr.insee.pogues.config.auth.UserProvider;
-import fr.insee.pogues.config.auth.user.User;
-import fr.insee.pogues.persistence.service.QuestionnairesService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
-import io.swagger.v3.oas.annotations.security.SecurityRequirement;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.ArrayList;
+import java.util.List;
/**
* WebService class for the Instrument Persistence
@@ -52,6 +60,9 @@ public class PoguesPersistence {
@Autowired
private QuestionnairesService questionnaireService;
+ @Autowired
+ private VariablesService variablesService;
+
@Autowired
private Environment env;
@@ -79,7 +90,7 @@ public ResponseEntity