Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f6a853e
Java 17 update
JoshuaForssmanNedap Jun 12, 2026
be5b97a
Update documentation
JoshuaForssmanNedap Jun 12, 2026
6ce4edb
Test workflows
JoshuaForssmanNedap Jun 12, 2026
a06f375
Test workflows
JoshuaForssmanNedap Jun 12, 2026
847d09f
Test workflows
JoshuaForssmanNedap Jun 12, 2026
f02bb35
Merge branch '670_feature_archie_4_major_version' into 793_java_17
MattijsK Jun 12, 2026
ae440ca
Revert change
JoshuaForssmanNedap Jun 15, 2026
41a067e
Merge remote-tracking branch 'origin/793_java_17' into 793_java_17
JoshuaForssmanNedap Jun 15, 2026
e9551e3
semaphore remove java 11 jobs
JoshuaForssmanNedap Jun 16, 2026
a7ea97c
Merge remote-tracking branch 'origin/670_feature_archie_4_major_versi…
JoshuaForssmanNedap Jun 16, 2026
cbe0f2a
Merge branch '670_feature_archie_4_major_version' into 793_java_17
MattijsK Jun 17, 2026
82ad425
Jackson 3 implementation changes
JoshuaForssmanNedap Jun 17, 2026
ac57787
Adding tests for coverage
JoshuaForssmanNedap Jun 18, 2026
b87a0c0
Adding tests for coverage
JoshuaForssmanNedap Jun 18, 2026
c765ee3
Adding jackson 3 test coverage
JoshuaForssmanNedap Jun 18, 2026
537672a
Adding more test coverage
JoshuaForssmanNedap Jun 18, 2026
ed6e52a
Adding more test coverage
JoshuaForssmanNedap Jun 19, 2026
f1f8731
Merge branch '670_feature_archie_4_major_version' into 5480_jackson_3…
JoshuaForssmanNedap Jun 23, 2026
62c0aac
Updated documentation
JoshuaForssmanNedap Jun 24, 2026
69a2139
Jackson 3 implementation changes
JoshuaForssmanNedap Jun 17, 2026
4ae68bb
Adding tests for coverage
JoshuaForssmanNedap Jun 18, 2026
49d4629
Adding tests for coverage
JoshuaForssmanNedap Jun 18, 2026
77f92e1
Adding jackson 3 test coverage
JoshuaForssmanNedap Jun 18, 2026
776d48f
Adding more test coverage
JoshuaForssmanNedap Jun 18, 2026
28e98c9
Adding more test coverage
JoshuaForssmanNedap Jun 19, 2026
fade233
Update language files
MattijsK Jun 17, 2026
35eb355
Implement CTerminologyCode.constraint as String (#761)
MattijsK Jun 18, 2026
02ff5ff
Merge remote-tracking branch 'origin/5480_jackson_3_support' into 548…
JoshuaForssmanNedap Jun 24, 2026
369d176
Update test after merge from parent branch.
JoshuaForssmanNedap Jun 25, 2026
68faf5a
Merge branch '670_feature_archie_4_major_version' into 5480_jackson_3…
JoshuaForssmanNedap Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions aom/src/main/java/com/nedap/archie/aom/ResourceDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.JsonNode;

import javax.annotation.Nullable;
import java.util.ArrayList;
Expand Down Expand Up @@ -102,22 +101,21 @@ public void setLifecycleState(String lifecycleState) {
*
* This deserializer ensures backward compatibility by accepting both formats
* and always setting the lifecycle state value as a plain String.
* Object parameter instead of JsonNode keeps this compatible with both Jackson 2 and 3.
*/
@JsonSetter("lifecycle_state")
private void setLifecycleState(JsonNode node) {
if (node == null || node.isNull()) {
private void setLifecycleState(Object node) {
if (node == null) {
this.lifecycleState = null;
} else if (node instanceof String) {
this.lifecycleState = (String) node;
} else if (node instanceof Map) {
Object codeString = ((Map<?, ?>) node).get("code_string");
this.lifecycleState = (codeString != null) ? codeString.toString() : null;
} else {
// Empty list or other unexpected type: treat as absent
this.lifecycleState = null;
return;
}

if (node.isTextual()) {
this.lifecycleState = node.asText();
return;
}

JsonNode codeString = node.get("code_string");

this.lifecycleState = (codeString != null && !codeString.isNull()) ? codeString.asText() : null;
}

public String getCustodianNamespace() {
Expand Down
41 changes: 20 additions & 21 deletions aom/src/main/java/com/nedap/archie/rules/ExpressionType.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.nedap.archie.rules;

import com.fasterxml.jackson.annotation.JsonCreator;

/**
* TODO: this should contain all primitive types and primitive types should be merged into this
* Created by pieter.bos on 27/10/15.
*/
public enum ExpressionType {
BOOLEAN, STRING, INTEGER, REAL, DATE, TIME, DATETIME, DURATION, C_STRING;

@JsonCreator
public static ExpressionType fromString(String string) {
switch(string) {
case "Boolean":
Expand All @@ -28,30 +31,26 @@ public static ExpressionType fromString(String string) {
case "CString":
return C_STRING;
}
// Fall back to case-insensitive match against enum constant names (e.g. "C_STRING")
for (ExpressionType type : values()) {
if (type.name().equalsIgnoreCase(string)) {
return type;
}
}
return null;
}

public String toString() {
switch(this) {
case BOOLEAN:
return "Boolean";
case STRING:
return "String";
case INTEGER:
return "Integer";
case REAL:
return "Real";
case DATE:
return "Date";
case TIME:
return "Time";
case DATETIME:
return "DateTime";
case DURATION:
return "Duration";
case C_STRING:
return "CString";
}
return null;
return switch (this) {
case BOOLEAN -> "Boolean";
case STRING -> "String";
case INTEGER -> "Integer";
case REAL -> "Real";
case DATE -> "Date";
case TIME -> "Time";
case DATETIME -> "DateTime";
case DURATION -> "Duration";
case C_STRING -> "CString";
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.nedap.archie.rminfo.RMObjectMapperProvider;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* @author markopi
Expand All @@ -12,17 +13,23 @@ abstract public class ADLArchetypeSerializer<T extends Archetype> {
protected final T archetype;
final RMObjectMapperProvider rmObjectMapperProvider;
Function<String, Archetype> flatArchetypeProvider;
protected final ADLStringBuilder builder = new ADLStringBuilder();
protected final ADLStringBuilder builder;

private final ADLDefinitionSerializer definitionSerializer;
private final ADLRulesSerializer rulesSerializer;


// Default constructor — uses the Jackson 2 backed ADLStringBuilder.
protected ADLArchetypeSerializer(T archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider) {
this(archetype, flatArchetypeProvider, rmObjectMapperProvider, new ADLStringBuilder());
}

// Use this constructor to supply a custom ADLStringBuilder, e.g. new ADLStringBuilder3() for Jackson 3.
protected ADLArchetypeSerializer(T archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, ADLStringBuilder builder) {
this.archetype = archetype;
this.flatArchetypeProvider = flatArchetypeProvider;
this.rmObjectMapperProvider = rmObjectMapperProvider;

this.builder = builder;
this.definitionSerializer = new ADLDefinitionSerializer(builder, flatArchetypeProvider, rmObjectMapperProvider);
this.rulesSerializer = new ADLRulesSerializer(builder, definitionSerializer);
}
Expand Down Expand Up @@ -51,6 +58,23 @@ public static String serialize(Archetype archetype, Function<String, Archetype>
}


/**
* Serialize the archetype to ADL, using a custom ADLStringBuilder (e.g. one backed by Jackson 3).
*/
public static String serialize(Archetype archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, Supplier<ADLStringBuilder> builderSupplier) {
if (archetype instanceof Template) {
return new ADLTemplateSerializer((Template) archetype, flatArchetypeProvider, rmObjectMapperProvider, builderSupplier.get()).serialize();
} else if (archetype instanceof OperationalTemplate) {
return new ADLOperationalTemplateSerializer((OperationalTemplate) archetype, flatArchetypeProvider, rmObjectMapperProvider, builderSupplier.get()).serialize();
} else if (archetype instanceof TemplateOverlay) {
return new ADLTemplateOverlaySerializer((TemplateOverlay) archetype, flatArchetypeProvider, rmObjectMapperProvider, builderSupplier.get()).serialize();
} else if (archetype instanceof AuthoredArchetype) {
return new ADLAuthoredArchetypeSerializer<>((AuthoredArchetype) archetype, flatArchetypeProvider, rmObjectMapperProvider, builderSupplier.get()).serialize();
}
throw new AssertionError("Could not serialize archetype of class " +
(archetype == null ? null : archetype.getClass().getName()));
}

/**
* Serialize the archetype to ADL
* @param archetype the archetype to serialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
*/
public class ADLAuthoredArchetypeSerializer<T extends AuthoredArchetype> extends ADLArchetypeSerializer<T> {

// Default constructor — uses the Jackson 2 backed ADLStringBuilder.
public ADLAuthoredArchetypeSerializer(T archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider);
}

// Use this constructor to supply a custom ADLStringBuilder, e.g. new ADLStringBuilder3() for Jackson 3.
public ADLAuthoredArchetypeSerializer(T archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, ADLStringBuilder builder) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider, builder);
}

@Override
protected void appendLanguage() {
if (archetype.getOriginalLanguage() == null) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
*/
class ADLOperationalTemplateSerializer extends ADLAuthoredArchetypeSerializer<OperationalTemplate> {

// Default constructor — uses the Jackson 2 backed ADLStringBuilder.
public ADLOperationalTemplateSerializer(OperationalTemplate archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider);
}

// Use this constructor to supply a custom ADLStringBuilder, e.g. new ADLStringBuilder3() for Jackson 3.
public ADLOperationalTemplateSerializer(OperationalTemplate archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, ADLStringBuilder builder) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider, builder);
}

@Override
protected String serialize() {
super.serialize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public ADLStringBuilder indent() {

public ADLStringBuilder odin(Object structure) {
try {
// Pass the current ident depth to the ODINPrettyPrinter
// Pass the current indent depth to the ODINPrettyPrinter
ObjectWriter objectWriter = odinMapper.writer().with(new ODINPrettyPrinter(builder.getIndentDepth()));

String odin = objectWriter.writeValueAsString(structure).trim();
Expand Down Expand Up @@ -138,8 +138,4 @@ public int getIndentDepth() {
return builder.getIndentDepth();
}

public ODINMapper getOdinMapper() {
return odinMapper;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
class ADLTemplateOverlaySerializer extends ADLArchetypeSerializer<TemplateOverlay> {


// Default constructor — uses the Jackson 2 backed ADLStringBuilder.
public ADLTemplateOverlaySerializer(TemplateOverlay archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider);
}

// Use this constructor to supply a custom ADLStringBuilder, e.g. new ADLStringBuilder3() for Jackson 3.
public ADLTemplateOverlaySerializer(TemplateOverlay archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, ADLStringBuilder builder) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider, builder);
}

@Override
protected String headTag() {
return "template_overlay";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
*/
class ADLTemplateSerializer extends ADLAuthoredArchetypeSerializer<Template> {

// Default constructor — uses the Jackson 2 backed ADLStringBuilder.
public ADLTemplateSerializer(Template archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider);
}

// Use this constructor to supply a custom ADLStringBuilder, e.g. new ADLStringBuilder3() for Jackson 3.
public ADLTemplateSerializer(Template archetype, Function<String, Archetype> flatArchetypeProvider, RMObjectMapperProvider rmObjectMapperProvider, ADLStringBuilder builder) {
super(archetype, flatArchetypeProvider, rmObjectMapperProvider, builder);
}

@Override
protected String headTag() {
return "template";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.nedap.archie.serializer.adl.jackson3;

import com.nedap.archie.serializer.adl.ADLStringBuilder;
import org.openehr.odin.jackson3.ODINMapper3;
import org.openehr.odin.jackson3.ODINPrettyPrinter3;

/**
* Jackson 3 variant of {@link ADLStringBuilder}.
* Identical behaviour, but uses ODINMapper3 for ODIN serialization instead of the Jackson 2 ODINMapper.
*/
public class ADLStringBuilder3 extends ADLStringBuilder {

private final ODINMapper3 odinMapper;

public ADLStringBuilder3() {
odinMapper = (ODINMapper3) new ArchetypeODINMapperFactory3().createMapper();
}

@Override
public ADLStringBuilder odin(Object structure) {
try {
String odin = odinMapper.writer()
.with(new ODINPrettyPrinter3(getIndentDepth()))
.writeValueAsString(structure).trim();
append(odin).newline();
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nedap.archie.serializer.adl.jackson3;

import com.nedap.archie.aom.terminology.ArchetypeTerm;
import com.nedap.archie.aom.terminology.ArchetypeTerminology;
import com.nedap.archie.serializer.adl.jackson.ArchetypeTerminologyMixin;
import org.openehr.odin.jackson3.ODINMapper3;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.module.SimpleModule;

public class ArchetypeODINMapperFactory3 {

public ObjectMapper createMapper() {
SimpleModule module = new SimpleModule("archetype-odin-module");
module.addSerializer(ArchetypeTerm.class, new ArchetypeTermOdinSerializer());
module.setMixInAnnotation(ArchetypeTerminology.class, ArchetypeTerminologyMixin.class);

return ODINMapper3.builder()
.deactivateDefaultTyping()
.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
.addModule(module)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.nedap.archie.serializer.adl.jackson3;

import com.nedap.archie.aom.terminology.ArchetypeTerm;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;

import java.util.Map;

/**
* ArchetypeTerm implements Map, so gets serialized with the Mapserializer instead of object. Do this manually
*/
public class ArchetypeTermOdinSerializer extends ValueSerializer<ArchetypeTerm> {

@Override
public void serialize(ArchetypeTerm value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
gen.writeStartObject();
gen.writeName("text");
gen.writeString(value.getText());
if (value.getDescription() != null) {
gen.writeName("description");
gen.writeString(value.getDescription());
}
for (Map.Entry<String, String> entry : value.getOtherItems().entrySet()) {
gen.writeName(entry.getKey());
gen.writeString(entry.getValue());
}
gen.writeEndObject();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.nedap.archie.json;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize(converter = RulesSectionToListConverter.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(converter = RulesSectionToListConverter.class)
@tools.jackson.databind.annotation.JsonSerialize(converter = com.nedap.archie.json3.RulesSectionToListConverter.class)
public interface RulesSectionMixin {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.nedap.archie.json3;

import org.threeten.extra.PeriodDuration;
import tools.jackson.databind.module.SimpleModule;

import java.time.Duration;
import java.time.Period;
import java.time.temporal.TemporalAmount;

/**
* A Jackson module that overrides the standard JavaTimeModule serializers and deserializers for ISO 8601 Durations.
* It supports the negative format as used in OpenEHR, and it can parse PeriodDurations if required
*/
public class ArchieDurationModule extends SimpleModule {

public ArchieDurationModule() {
super("archie-duration-module");

addDeserializer(TemporalAmount.class, new DurationDeserializer());

addSerializer(Duration.class, new DurationSerializer());
addSerializer(TemporalAmount.class, new DurationSerializer());
addSerializer(Period.class, new DurationSerializer());
addSerializer(PeriodDuration.class, new DurationSerializer());
}
}
Loading