diff --git a/aom/src/main/java/com/nedap/archie/aom/Archetype.java b/aom/src/main/java/com/nedap/archie/aom/Archetype.java index 0e64a465b..368bf7eba 100644 --- a/aom/src/main/java/com/nedap/archie/aom/Archetype.java +++ b/aom/src/main/java/com/nedap/archie/aom/Archetype.java @@ -14,6 +14,7 @@ import com.nedap.archie.xml.adapters.ArchetypeTerminologyAdapter; import com.nedap.archie.xml.adapters.RMOverlayXmlAdapter; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -55,6 +56,7 @@ public class Archetype extends AuthoredResource { @XmlElement(name="parent_archetype_id") + @Nullable private String parentArchetypeId; @XmlAttribute(name="is_differential") private boolean differential = false; @@ -64,9 +66,11 @@ public class Archetype extends AuthoredResource { private CComplexObject definition; @XmlJavaTypeAdapter(ArchetypeTerminologyAdapter.class) private ArchetypeTerminology terminology; + @Nullable private RulesSection rules = null; @XmlAttribute(name="adl_version") + @Nullable private String adlVersion; @XmlElement(name="build_uid") private String buildUid; diff --git a/aom/src/main/java/com/nedap/archie/aom/ArchetypeConstraint.java b/aom/src/main/java/com/nedap/archie/aom/ArchetypeConstraint.java index f8cf92f4b..743c25540 100644 --- a/aom/src/main/java/com/nedap/archie/aom/ArchetypeConstraint.java +++ b/aom/src/main/java/com/nedap/archie/aom/ArchetypeConstraint.java @@ -4,6 +4,7 @@ import com.nedap.archie.paths.PathSegment; import com.nedap.archie.paths.PathUtil; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlTransient; import java.util.List; @@ -14,7 +15,9 @@ public abstract class ArchetypeConstraint extends ArchetypeModelObject { @JsonIgnore //ignore these field in popular object mappers + @Nullable private transient ArchetypeConstraint parent; + @Nullable @JsonIgnore //ignore these field in popular object mappers, otherwise we get infinite loops private transient CSecondOrder socParent; diff --git a/aom/src/main/java/com/nedap/archie/aom/ArchetypeHRID.java b/aom/src/main/java/com/nedap/archie/aom/ArchetypeHRID.java index 9b6a60b7a..280711129 100644 --- a/aom/src/main/java/com/nedap/archie/aom/ArchetypeHRID.java +++ b/aom/src/main/java/com/nedap/archie/aom/ArchetypeHRID.java @@ -7,6 +7,7 @@ import com.nedap.archie.definitions.VersionStatus; import com.nedap.archie.rminfo.RMPropertyIgnore; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -32,6 +33,7 @@ }) public class ArchetypeHRID extends ArchetypeModelObject { + @Nullable private String namespace; @XmlAttribute(name="rm_publisher") private String rmPublisher; diff --git a/aom/src/main/java/com/nedap/archie/aom/ArchetypeSlot.java b/aom/src/main/java/com/nedap/archie/aom/ArchetypeSlot.java index 7a26c4ce7..6ed99f7b7 100644 --- a/aom/src/main/java/com/nedap/archie/aom/ArchetypeSlot.java +++ b/aom/src/main/java/com/nedap/archie/aom/ArchetypeSlot.java @@ -2,6 +2,7 @@ import com.nedap.archie.rules.Assertion; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import java.util.ArrayList; @@ -17,7 +18,9 @@ }) public class ArchetypeSlot extends CObject { + @Nullable private List includes = new ArrayList<>(); + @Nullable private List excludes = new ArrayList<>(); @XmlElement(name="is_closed") private boolean closed = false; diff --git a/aom/src/main/java/com/nedap/archie/aom/CAttribute.java b/aom/src/main/java/com/nedap/archie/aom/CAttribute.java index 79d644735..a6416b5f1 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CAttribute.java +++ b/aom/src/main/java/com/nedap/archie/aom/CAttribute.java @@ -9,6 +9,7 @@ import com.nedap.archie.paths.PathSegment; import com.nedap.archie.query.APathQuery; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -35,14 +36,18 @@ public class CAttribute extends ArchetypeConstraint { @XmlAttribute(name="rm_attribute_name") private String rmAttributeName; + @Nullable private MultiplicityInterval existence; @XmlElement(name="differential_path") + @Nullable private String differentialPath; @XmlElement(name="is_multiple") private boolean multiple; + @Nullable private Cardinality cardinality; + @Nullable private List children = new ArrayList<>(); public CAttribute() { diff --git a/aom/src/main/java/com/nedap/archie/aom/CAttributeTuple.java b/aom/src/main/java/com/nedap/archie/aom/CAttributeTuple.java index 013180a69..57ffc7701 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CAttributeTuple.java +++ b/aom/src/main/java/com/nedap/archie/aom/CAttributeTuple.java @@ -4,6 +4,7 @@ import com.nedap.archie.rminfo.ModelInfoLookup; import com.nedap.archie.rminfo.RMAttributeInfo; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlType; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -28,6 +29,7 @@ public class CAttributeTuple extends CSecondOrder { * The members List of CSecondOrder contains the attribute names. */ + @Nullable private List tuples = new ArrayList<>(); public List getTuples() { diff --git a/aom/src/main/java/com/nedap/archie/aom/CComplexObject.java b/aom/src/main/java/com/nedap/archie/aom/CComplexObject.java index 9cb60eb47..a5906689b 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CComplexObject.java +++ b/aom/src/main/java/com/nedap/archie/aom/CComplexObject.java @@ -5,6 +5,7 @@ import com.nedap.archie.base.OpenEHRBase; import com.nedap.archie.query.AOMPathQuery; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -24,9 +25,11 @@ }) public class CComplexObject extends CDefinedObject { + @Nullable private List attributes = new ArrayList<>(); @XmlElement(name="attributeTuples") + @Nullable private List attributeTuples = new ArrayList<>(); @JsonIgnore diff --git a/aom/src/main/java/com/nedap/archie/aom/CDefinedObject.java b/aom/src/main/java/com/nedap/archie/aom/CDefinedObject.java index 1c520eaf6..fd646de62 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CDefinedObject.java +++ b/aom/src/main/java/com/nedap/archie/aom/CDefinedObject.java @@ -1,5 +1,6 @@ package com.nedap.archie.aom; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @@ -13,6 +14,7 @@ public abstract class CDefinedObject extends CObject { @XmlElement(name="default_value") //TODO: this will not deserialize, it needs possible classes + @Nullable private T defaultValue; public T getDefaultValue() { diff --git a/aom/src/main/java/com/nedap/archie/aom/CObject.java b/aom/src/main/java/com/nedap/archie/aom/CObject.java index 03f22e7f4..df8f0f551 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CObject.java +++ b/aom/src/main/java/com/nedap/archie/aom/CObject.java @@ -10,6 +10,7 @@ import com.nedap.archie.paths.PathSegment; import org.openehr.utils.message.I18n; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -40,13 +41,16 @@ public abstract class CObject extends ArchetypeConstraint { @XmlAttribute(name="rm_type_name") private String rmTypeName; @XmlElement(name="occurrences") + @Nullable private MultiplicityInterval occurrences; @XmlAttribute(name="node_id") private String nodeId; @XmlAttribute(name="is_deprecated") + @Nullable private Boolean deprecated; @XmlElement(name="sibling_order") + @Nullable private SiblingOrder siblingOrder; diff --git a/aom/src/main/java/com/nedap/archie/aom/CPrimitiveObject.java b/aom/src/main/java/com/nedap/archie/aom/CPrimitiveObject.java index b661fdb39..22af92dd3 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CPrimitiveObject.java +++ b/aom/src/main/java/com/nedap/archie/aom/CPrimitiveObject.java @@ -7,6 +7,7 @@ import org.openehr.utils.message.I18n; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlType; @@ -26,8 +27,10 @@ public abstract class CPrimitiveObject extends CDefinedOb public static final String PRIMITIVE_NODE_ID_VALUE = "id9999"; + @Nullable private Boolean enumeratedTypeConstraint; + @Nullable public abstract ValueType getAssumedValue(); public abstract void setAssumedValue(ValueType assumedValue); diff --git a/aom/src/main/java/com/nedap/archie/aom/CSecondOrder.java b/aom/src/main/java/com/nedap/archie/aom/CSecondOrder.java index 03608eccf..7ce548b36 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CSecondOrder.java +++ b/aom/src/main/java/com/nedap/archie/aom/CSecondOrder.java @@ -1,5 +1,6 @@ package com.nedap.archie.aom; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlType; import java.util.ArrayList; import java.util.List; @@ -9,6 +10,7 @@ */ @XmlType(name="C_SECOND_ORDER") public class CSecondOrder extends ArchetypeModelObject { + @Nullable private List members = new ArrayList<>(); public T getMember(int i) { diff --git a/aom/src/main/java/com/nedap/archie/aom/OperationalTemplate.java b/aom/src/main/java/com/nedap/archie/aom/OperationalTemplate.java index 14f1cd24a..5fdcf8ff5 100644 --- a/aom/src/main/java/com/nedap/archie/aom/OperationalTemplate.java +++ b/aom/src/main/java/com/nedap/archie/aom/OperationalTemplate.java @@ -1,7 +1,10 @@ package com.nedap.archie.aom; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; + +import com.esotericsoftware.kryo.kryo5.util.Null; import com.nedap.archie.aom.terminology.ArchetypeTerm; import com.nedap.archie.aom.terminology.ArchetypeTerminology; import com.nedap.archie.aom.utils.AOMUtils; @@ -27,8 +30,10 @@ public class OperationalTemplate extends AuthoredArchetype { * terminology extracts from subarchetypes, for example snomed codes, multiple choice thingies, etc */ @XmlElement(name="terminology_extracts") //TODO: requires an adapter for JAXB to work + @Nullable private Map terminologyExtracts = new ConcurrentHashMap<>();//TODO: is this correct? @XmlElement(name="component_terminologies") //TODO: requires an adapter for JAXB to work + @Nullable private Map componentTerminologies = new ConcurrentHashMap<>(); diff --git a/aom/src/main/java/com/nedap/archie/aom/ResourceDescription.java b/aom/src/main/java/com/nedap/archie/aom/ResourceDescription.java index 40aae47b1..9a1594a85 100644 --- a/aom/src/main/java/com/nedap/archie/aom/ResourceDescription.java +++ b/aom/src/main/java/com/nedap/archie/aom/ResourceDescription.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.nedap.archie.base.terminology.TerminologyCode; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -15,20 +16,33 @@ public class ResourceDescription extends ArchetypeModelObject { private Map originalAuthor = new ConcurrentHashMap<>(); + @Nullable private String originalNamespace; + @Nullable private String originalPublisher; + @Nullable private List otherContributors = new ArrayList<>(); private TerminologyCode lifecycleState; + @Nullable private String custodianNamespace; + @Nullable private String custodianOrganisation; + @Nullable private String copyright; + @Nullable private String licence; + @Nullable private Map ipAcknowledgements = new ConcurrentHashMap<>(); + @Nullable private Map references = new ConcurrentHashMap<>(); + @Nullable private String resourcePackageUri; + @Nullable private Map conversionDetails = new ConcurrentHashMap<>(); + @Nullable private Map otherDetails = new ConcurrentHashMap<>(); + @Nullable private Map details = new ConcurrentHashMap<>(); public Map getOriginalAuthor() { diff --git a/aom/src/main/java/com/nedap/archie/aom/ResourceDescriptionItem.java b/aom/src/main/java/com/nedap/archie/aom/ResourceDescriptionItem.java index f39b7da19..6276a264d 100644 --- a/aom/src/main/java/com/nedap/archie/aom/ResourceDescriptionItem.java +++ b/aom/src/main/java/com/nedap/archie/aom/ResourceDescriptionItem.java @@ -2,6 +2,7 @@ import com.nedap.archie.base.terminology.TerminologyCode; +import javax.annotation.Nullable; import java.net.URI; import java.util.List; import java.util.Map; @@ -12,10 +13,15 @@ public class ResourceDescriptionItem extends ArchetypeModelObject { private TerminologyCode language; private String purpose; + @Nullable private List keywords; + @Nullable private String use; + @Nullable private String misuse; + @Nullable private String copyright; + @Nullable private Map originalResourceUri; private Map otherDetails;//TODO: string -> object? diff --git a/aom/src/main/java/com/nedap/archie/aom/Template.java b/aom/src/main/java/com/nedap/archie/aom/Template.java index 9f85ce0ad..1c5d92ebb 100644 --- a/aom/src/main/java/com/nedap/archie/aom/Template.java +++ b/aom/src/main/java/com/nedap/archie/aom/Template.java @@ -1,5 +1,8 @@ package com.nedap.archie.aom; +import com.fasterxml.jackson.annotation.JsonAlias; + +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @@ -15,8 +18,10 @@ @XmlRootElement(name="archetype") public class Template extends AuthoredArchetype { + @Nullable private List templateOverlays = new ArrayList<>(); + @JsonAlias("overlays") public List getTemplateOverlays() { return templateOverlays; } diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CBoolean.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CBoolean.java index f77fe681c..2918dda27 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CBoolean.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CBoolean.java @@ -22,6 +22,7 @@ @XmlType(name="C_BOOLEAN") public class CBoolean extends CPrimitiveObject { @XmlElement(name="assumed_value") + @Nullable private Boolean assumedValue; @Nullable private List constraint = new ArrayList<>(); diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CDate.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CDate.java index 953812830..7f37fd2b0 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CDate.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CDate.java @@ -4,6 +4,7 @@ import com.nedap.archie.xml.adapters.DateIntervalXmlAdapter; import com.nedap.archie.xml.adapters.DateXmlAdapter; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -22,8 +23,10 @@ public class CDate extends CTemporal { @XmlJavaTypeAdapter(DateXmlAdapter.class) @XmlElement(name="assumed_value") + @Nullable private Temporal assumedValue; @XmlJavaTypeAdapter(DateIntervalXmlAdapter.class) + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CDateTime.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CDateTime.java index c8c4a5766..77a97bdb7 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CDateTime.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CDateTime.java @@ -4,6 +4,7 @@ import com.nedap.archie.xml.adapters.DateTimeIntervalXmlAdapter; import com.nedap.archie.xml.adapters.DateTimeXmlAdapter; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -22,8 +23,10 @@ public class CDateTime extends CTemporal { @XmlJavaTypeAdapter(DateTimeXmlAdapter.class) @XmlElement(name="assumed_value") + @Nullable private TemporalAccessor assumedValue; @XmlJavaTypeAdapter(DateTimeIntervalXmlAdapter.class) + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CDuration.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CDuration.java index b6882ddb6..ff6f9e346 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CDuration.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CDuration.java @@ -4,6 +4,7 @@ import com.nedap.archie.xml.adapters.DurationIntervalXmlAdapter; import com.nedap.archie.xml.adapters.DurationXmlAdapter; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -22,8 +23,10 @@ public class CDuration extends CTemporal { @XmlJavaTypeAdapter(DurationXmlAdapter.class) @XmlElement(name="assumed_value") + @Nullable private TemporalAmount assumedValue; @XmlJavaTypeAdapter(DurationIntervalXmlAdapter.class) + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CInteger.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CInteger.java index da5849bb2..1936485e2 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CInteger.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CInteger.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.nedap.archie.base.Interval; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -19,7 +20,9 @@ @XmlAccessorType(XmlAccessType.FIELD) public class CInteger extends COrdered { @XmlElement(name="assumed_value") + @Nullable private Long assumedValue; + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CReal.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CReal.java index fd0e75f9b..ecf1efd4c 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CReal.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CReal.java @@ -2,6 +2,7 @@ import com.nedap.archie.base.Interval; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -18,7 +19,9 @@ public class CReal extends COrdered { @XmlElement(name="assumed_value") + @Nullable private Double assumedValue; + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CString.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CString.java index c3ac2c2bd..b52385baf 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CString.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CString.java @@ -7,6 +7,7 @@ import com.nedap.archie.archetypevalidator.ErrorType; import org.openehr.utils.message.I18n; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -24,7 +25,9 @@ public class CString extends CPrimitiveObject { @XmlElement(name="assumed_value") + @Nullable private String assumedValue; + @Nullable private List constraint = new ArrayList<>(); public CString() { diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CTemporal.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CTemporal.java index a843f4394..ba31472d2 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CTemporal.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CTemporal.java @@ -1,11 +1,14 @@ package com.nedap.archie.aom.primitives; +import javax.annotation.Nullable; + /** * TODO: cConformsTo for temporal and date types * Created by pieter.bos on 15/10/15. */ public abstract class CTemporal extends COrdered{ + @Nullable private String patternedConstraint; public String getPatternedConstraint() { diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CTerminologyCode.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CTerminologyCode.java index 98146f472..4be72cd31 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CTerminologyCode.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CTerminologyCode.java @@ -18,6 +18,7 @@ import com.nedap.archie.rminfo.ModelInfoLookup; import org.openehr.utils.message.I18n; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -38,9 +39,11 @@ public class CTerminologyCode extends CPrimitiveObject { @XmlElement(name="assumed_value") + @Nullable private TerminologyCode assumedValue; private List constraint = new ArrayList<>(); + @Nullable private ConstraintStatus constraintStatus; @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/primitives/CTime.java b/aom/src/main/java/com/nedap/archie/aom/primitives/CTime.java index 065aecd3f..5099be6ff 100644 --- a/aom/src/main/java/com/nedap/archie/aom/primitives/CTime.java +++ b/aom/src/main/java/com/nedap/archie/aom/primitives/CTime.java @@ -4,6 +4,7 @@ import com.nedap.archie.xml.adapters.TimeIntervalXmlAdapter; import com.nedap.archie.xml.adapters.TimeXmlAdapter; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -22,8 +23,10 @@ public class CTime extends CTemporal { @XmlJavaTypeAdapter(TimeXmlAdapter.class) @XmlElement(name="assumed_value") + @Nullable private TemporalAccessor assumedValue; @XmlJavaTypeAdapter(TimeIntervalXmlAdapter.class) + @Nullable private List> constraint = new ArrayList<>(); @Override diff --git a/aom/src/main/java/com/nedap/archie/aom/profile/AomProfile.java b/aom/src/main/java/com/nedap/archie/aom/profile/AomProfile.java index c24f2071e..055a1adf2 100644 --- a/aom/src/main/java/com/nedap/archie/aom/profile/AomProfile.java +++ b/aom/src/main/java/com/nedap/archie/aom/profile/AomProfile.java @@ -1,5 +1,6 @@ package com.nedap.archie.aom.profile; +import javax.annotation.Nullable; import java.util.List; import java.util.Map; @@ -17,8 +18,10 @@ public class AomProfile { */ private String profileName; + @Nullable private List rmSchemaPattern; + @Nullable private String archetypeVisualiseDescendantsOf; /** @@ -26,19 +29,23 @@ public class AomProfile { * for them in an archetype. E.g. <value = "String", key = "ISO8601_DATE"> means that if RM property * TYPE.some_property is of type String, an ISO8601_DATE is allowed at that position in the archetype. */ + @Nullable private Map aomRmTypeSubstitutions; /** * List of mappings of lifecycle state names used in archetypes to AOM lifecycle state names. value = AOM * lifecycle state; key = source lifecycle state. */ + @Nullable private Map aomLifecycleMappings; /** * Mappings from AOM built-in types to actual types in RM: whenever the type name is encountered in * an archetype, it is mapped to a specific RM type. */ + @Nullable private Map aomRmTypeMappings; + @Nullable private Map rmPrimitiveTypeEquivalences; private AomTerminologyProfile terminologyProfile; diff --git a/aom/src/main/java/com/nedap/archie/aom/profile/AomTypeMapping.java b/aom/src/main/java/com/nedap/archie/aom/profile/AomTypeMapping.java index a986be0af..17e8d2535 100644 --- a/aom/src/main/java/com/nedap/archie/aom/profile/AomTypeMapping.java +++ b/aom/src/main/java/com/nedap/archie/aom/profile/AomTypeMapping.java @@ -1,5 +1,6 @@ package com.nedap.archie.aom.profile; +import javax.annotation.Nullable; import java.util.Map; /** @@ -20,6 +21,7 @@ public class AomTypeMapping { /** * List of mappings of properties of this type to another type. */ + @Nullable private Map propertyMappings; /** diff --git a/aom/src/main/java/com/nedap/archie/aom/terminology/ArchetypeTerminology.java b/aom/src/main/java/com/nedap/archie/aom/terminology/ArchetypeTerminology.java index 236e800d7..7f16ab93a 100644 --- a/aom/src/main/java/com/nedap/archie/aom/terminology/ArchetypeTerminology.java +++ b/aom/src/main/java/com/nedap/archie/aom/terminology/ArchetypeTerminology.java @@ -6,6 +6,7 @@ import com.nedap.archie.aom.utils.AOMUtils; +import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -35,11 +36,13 @@ public class ArchetypeTerminology extends ArchetypeModelObject { private Map> termDefinitions = new ConcurrentHashMap<>(); //@XmlElement(name="term_bindings") @XmlTransient//TODO! + @Nullable private Map> termBindings = new ConcurrentHashMap<>(); //@XmlElement(name="terminology_extracts") @XmlTransient//TODO! private Map> terminologyExtracts = new ConcurrentHashMap<>(); @XmlElement(name="value_sets") + @Nullable private Map valueSets = new ConcurrentHashMap<>(); @JsonIgnore diff --git a/aom/src/main/java/com/nedap/archie/rminfo/ArchieAOMInfoLookup.java b/aom/src/main/java/com/nedap/archie/rminfo/ArchieAOMInfoLookup.java index 34cc9492d..d90cad3e9 100644 --- a/aom/src/main/java/com/nedap/archie/rminfo/ArchieAOMInfoLookup.java +++ b/aom/src/main/java/com/nedap/archie/rminfo/ArchieAOMInfoLookup.java @@ -1,16 +1,13 @@ package com.nedap.archie.rminfo; -import com.nedap.archie.aom.Archetype; -import com.nedap.archie.aom.ArchetypeModelObject; -import com.nedap.archie.aom.CObject; -import com.nedap.archie.aom.CPrimitiveObject; +import com.nedap.archie.aom.*; import com.nedap.archie.aom.rmoverlay.RmAttributeVisibility; import com.nedap.archie.aom.rmoverlay.RmOverlay; +import com.nedap.archie.aom.terminology.ArchetypeTerm; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; /** * Created by pieter.bos on 06/07/16. @@ -19,6 +16,8 @@ public class ArchieAOMInfoLookup extends ReflectionModelInfoLookup { private static ArchieAOMInfoLookup instance; + private Set methodsToAddAsFields = new HashSet<>(); + public ArchieAOMInfoLookup() { super(new ArchieModelNamingStrategy(), ArchetypeModelObject.class, ArchieAOMInfoLookup.class.getClassLoader(), false /* no attributes without field */); @@ -33,6 +32,15 @@ public static ArchieAOMInfoLookup getInstance() { @Override protected void addTypes(Class baseClass) { + try { + methodsToAddAsFields = new HashSet<>(); + methodsToAddAsFields.add(AuthoredResource.class.getMethod("getTranslations")); + methodsToAddAsFields.add(AuthoredResource.class.getMethod("getOriginalLanguage")); + methodsToAddAsFields.add(ArchetypeTerm.class.getMethod("getDescription")); + methodsToAddAsFields.add(ArchetypeTerm.class.getMethod("getText")); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } addClass(com.nedap.archie.aom.primitives.COrdered.class); addClass(com.nedap.archie.aom.CSecondOrder.class); addClass(com.nedap.archie.aom.CAttributeTuple.class); @@ -161,4 +169,22 @@ public Collection getId() { return result; } + @Override + protected boolean shouldAddMethodWithoutField(Method method) { + if(method != null && methodsToAddAsFields.contains(method)) { + return true; + } else if(!this.isAddAttributesWithoutField()) { + return false; + } + if(method == null) { + return true; + } + //do not add invariants + if(method.getAnnotation(Invariant.class) != null) { + return false; + } + //do not add private or protected properties, they will result in errors + return Modifier.isPublic(method.getModifiers()) && method.getAnnotation(RMPropertyIgnore.class) == null; + } + } diff --git a/aom/src/main/java/com/nedap/archie/rminfo/ReflectionModelInfoLookup.java b/aom/src/main/java/com/nedap/archie/rminfo/ReflectionModelInfoLookup.java index b72332468..348f332f1 100644 --- a/aom/src/main/java/com/nedap/archie/rminfo/ReflectionModelInfoLookup.java +++ b/aom/src/main/java/com/nedap/archie/rminfo/ReflectionModelInfoLookup.java @@ -153,14 +153,14 @@ private void addAttributeInfo(Class clazz, RMTypeInfo typeInfo) { for(Field field: fieldsByName.values()) { addRMAttributeInfo(clazz, typeInfo, typeToken, field); } - if(addAttributesWithoutField) { - Set getters = ReflectionUtils.getAllMethods(clazz, (method) -> method.getName().startsWith("get") || method.getName().startsWith("is")); - for (Method getMethod : getters) { - if(shouldAdd(getMethod)) { - addRMAttributeInfo(clazz, typeInfo, typeToken, getMethod, fieldsByName); - } + + Set getters = ReflectionUtils.getAllMethods(clazz, (method) -> method.getName().startsWith("get") || method.getName().startsWith("is")); + for (Method getMethod : getters) { + if(shouldAddMethodWithoutField(getMethod)) { + addRMAttributeInfo(clazz, typeInfo, typeToken, getMethod, fieldsByName); } } + } private void addInvariantChecks(Class clazz, RMTypeInfo typeInfo) { @@ -179,6 +179,22 @@ private void addInvariantChecks(Class clazz, RMTypeInfo typeInfo) { } + protected boolean shouldAddMethodWithoutField(Method method) { + if(!this.addAttributesWithoutField) { + return false; + } + if(method == null) { + return true; + } + //do not add invariants + if(method.getAnnotation(Invariant.class) != null) { + return false; + } + //do not add private or protected properties, they will result in errors + return Modifier.isPublic(method.getModifiers()) && method.getAnnotation(RMPropertyIgnore.class) == null; + } + + protected boolean shouldAdd(Method method) { if(method == null) { return true; @@ -230,6 +246,7 @@ protected void addRMAttributeInfo(Class clazz, RMTypeInfo typeInfo, TypeToken typeInCollection, this.namingStrategy.getTypeName(typeInCollection), isNullable(clazz, getMethod, field), + !getMethod.getDeclaringClass().equals(clazz), getMethod, setMethod, addMethod, @@ -320,6 +337,7 @@ private void addRMAttributeInfo(Class clazz, RMTypeInfo typeInfo, TypeToken rawFieldType = fieldType.getRawType(); Class typeInCollection = getTypeInCollection(fieldType); if (setMethod != null && (shouldAdd(setMethod) && shouldAdd(getMethod))) { + RMAttributeInfo attributeInfo = new RMAttributeInfo( attributeName, field, @@ -327,6 +345,7 @@ private void addRMAttributeInfo(Class clazz, RMTypeInfo typeInfo, TypeToken getTypeInCollection(TypeToken fieldType) { return (Class) ((java.lang.reflect.TypeVariable) actualTypeArguments[0]).getBounds()[0]; } } + } else if (Map.class.isAssignableFrom(rawFieldType)) { + Type[] actualTypeArguments = ((ParameterizedType) fieldType.getType()).getActualTypeArguments(); + if (actualTypeArguments.length == 2) { + //the java reflection api is kind of tricky with types. This works for the archie RM, but may cause problems for other RMs. The fix is implementing more ways + //keytype is assumed to be string for everything for now + Type mapValueType = actualTypeArguments[1]; + if (mapValueType instanceof Class) { + return (Class) mapValueType; + } else if (mapValueType instanceof ParameterizedType) { + ParameterizedType parameterizedTypeInCollection = (ParameterizedType) mapValueType; + return (Class) parameterizedTypeInCollection.getRawType(); + } else if (mapValueType instanceof java.lang.reflect.TypeVariable) { + return (Class) ((java.lang.reflect.TypeVariable) mapValueType).getBounds()[0]; + } + } } else if(rawFieldType.isArray()) { return rawFieldType.getComponentType(); } @@ -499,4 +533,8 @@ public Object convertConstrainedPrimitiveToRMObject(Object object) { //TODO: this should take an AttributeInfo as param, so to be able to pick the right object return object; } + + public boolean isAddAttributesWithoutField() { + return addAttributesWithoutField; + } } diff --git a/base/src/main/java/com/nedap/archie/rminfo/RMAttributeInfo.java b/base/src/main/java/com/nedap/archie/rminfo/RMAttributeInfo.java index 728b2695f..2e51be7ee 100644 --- a/base/src/main/java/com/nedap/archie/rminfo/RMAttributeInfo.java +++ b/base/src/main/java/com/nedap/archie/rminfo/RMAttributeInfo.java @@ -3,6 +3,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; +import java.util.Map; /** * Created by pieter.bos on 25/03/16. @@ -19,19 +20,20 @@ public class RMAttributeInfo { private final Method addMethod; private final boolean nullable; private final boolean computed; + private boolean fromAncestor; - - public RMAttributeInfo(String name, Field field, Class type, Class typeInCollection, String typeNameInCollection, boolean nullable, Method getMethod, Method setMethod, Method addMethod, boolean computed) { + public RMAttributeInfo(String name, Field field, Class type, Class typeInCollection, String typeNameInCollection, boolean nullable, boolean fromAncestor, Method getMethod, Method setMethod, Method addMethod, boolean computed) { this.name = name; this.field = field; this.type = type; this.nullable = nullable; + this.fromAncestor = fromAncestor; this.getMethod = getMethod; this.setMethod = setMethod; this.addMethod = addMethod; + this.computed = computed; this.isMultipleValued = (type instanceof Class && Collection.class.isAssignableFrom(type)) || type.isArray(); - this.typeInCollection = typeInCollection; this.typeNameInCollection = typeNameInCollection; } @@ -84,4 +86,8 @@ public boolean isComputed() { public String getTypeNameInCollection() { return typeNameInCollection; } + + public boolean isFromAncestor() { + return fromAncestor; + } } diff --git a/bmm/src/main/java/org/openehr/bmm/core/BmmGenericType.java b/bmm/src/main/java/org/openehr/bmm/core/BmmGenericType.java index c3de87f2b..b0710cddb 100644 --- a/bmm/src/main/java/org/openehr/bmm/core/BmmGenericType.java +++ b/bmm/src/main/java/org/openehr/bmm/core/BmmGenericType.java @@ -35,6 +35,7 @@ */ public class BmmGenericType extends BmmDefinedType implements Serializable { + /** * Generic parameters of the root_type in this type specifier. The order must match the order of the owning class’s * formal generic parameter declarations. diff --git a/bmm/src/main/java/org/openehr/bmm/core/BmmTypeElement.java b/bmm/src/main/java/org/openehr/bmm/core/BmmTypeElement.java index 3f2741e1a..5f314275a 100644 --- a/bmm/src/main/java/org/openehr/bmm/core/BmmTypeElement.java +++ b/bmm/src/main/java/org/openehr/bmm/core/BmmTypeElement.java @@ -26,4 +26,5 @@ * Created by cnanjo on 1/24/17. */ public abstract class BmmTypeElement extends BmmEntity { + } diff --git a/bmm/src/main/java/org/openehr/bmm/v2/persistence/PBmmGenericType.java b/bmm/src/main/java/org/openehr/bmm/v2/persistence/PBmmGenericType.java index 4d9819f3a..60e1e2b18 100644 --- a/bmm/src/main/java/org/openehr/bmm/v2/persistence/PBmmGenericType.java +++ b/bmm/src/main/java/org/openehr/bmm/v2/persistence/PBmmGenericType.java @@ -32,6 +32,10 @@ public String getRootType() { return rootType; } + public void setRootType(String rootType) { + this.rootType = rootType; + } + /** * Effective unitary type, ignoring containers and also generic parameters */ diff --git a/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmClassProcessor.java b/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmClassProcessor.java index 835ae52c0..daef26748 100644 --- a/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmClassProcessor.java +++ b/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmClassProcessor.java @@ -2,7 +2,9 @@ import org.openehr.bmm.core.BmmClass; import org.openehr.bmm.core.BmmModel; + import org.openehr.bmm.core.BmmSimpleType; + import org.openehr.bmm.v2.persistence.PBmmClass; import org.openehr.bmm.v2.persistence.PBmmSchema; @@ -81,4 +83,5 @@ public BmmSimpleType getAnyTypeDefinition() { public BmmClass getUnprocessedClassDefinition(String name) { return model.getClassDefinition(name); } -} \ No newline at end of file + +} diff --git a/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmModelCreator.java b/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmModelCreator.java index 1605b91ce..07717c36e 100644 --- a/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmModelCreator.java +++ b/bmm/src/main/java/org/openehr/bmm/v2/validation/converters/BmmModelCreator.java @@ -3,6 +3,7 @@ import org.openehr.bmm.core.BmmClass; import org.openehr.bmm.core.BmmModel; import org.openehr.bmm.core.BmmPackage; +import org.openehr.bmm.core.BmmPackageContainer; import org.openehr.bmm.v2.persistence.PBmmClass; import org.openehr.bmm.v2.persistence.PBmmPackage; import org.openehr.bmm.v2.persistence.PBmmSchema; @@ -28,29 +29,18 @@ public BmmModel create(BmmValidationResult validationResult) { // Add packages first for(PBmmPackage pBmmPackage:validationResult.getCanonicalPackages().values()) { - BmmPackage bmmPackage = createBmmPackageDefinition (pBmmPackage, null, null); + BmmPackage bmmPackage = createBmmPackageDefinition(pBmmPackage, null, null); model.addPackage(bmmPackage); - - pBmmPackage.doRecursiveClasses((p, s) -> { - PBmmClass pBmmClass = schema.getClassDefinition(s); - if (pBmmClass != null) { - BmmClass bmmClass = pBmmClass.createBmmClass(); - if (bmmClass != null) { - bmmClass.setPrimitiveType(schema.getPrimitiveTypes().containsKey (bmmClass.getName())); - model.addClassDefinition(bmmClass, bmmPackage); - } - } - }); } + validationResult.getCanonicalPackages().forEach( (packageName, pBmmPackage) -> convertPackage(model, schema, pBmmPackage.getName(), pBmmPackage, model)); + model.setArchetypeParentClass (schema.getArchetypeParentClass()); model.setArchetypeDataValueParentClass (schema.getArchetypeDataValueParentClass()); model.setArchetypeVisualizeDescendantsOf (schema.getArchetypeVisualizeDescendantsOf()); model.setArchetypeRmClosurePackages (schema.getArchetypeRmClosurePackages() == null ? new ArrayList<>() : new ArrayList<>(schema.getArchetypeRmClosurePackages())); - - // The basics have been created. Now populate the classes with properties //setup all classes, ancestors and generic parameters BmmClassProcessor classSupplier = new BmmClassProcessor(model, schema, (pBmmClass, processor) -> pBmmClass.populateBmmClass(processor, schema)); classSupplier.run(); @@ -61,6 +51,26 @@ public BmmModel create(BmmValidationResult validationResult) { return model; } + private void convertPackage(BmmModel model, PBmmSchema schema, String packageName, PBmmPackage pBmmPackage, BmmPackageContainer bmmParentPackage) { + + BmmPackage bmmPackage = bmmParentPackage.getPackage(pBmmPackage.getName()); + pBmmPackage.getClasses().forEach( className -> { + + PBmmClass pBmmClass = schema.getClassDefinition(className); + if (pBmmClass != null) { + BmmClass bmmClass = pBmmClass.createBmmClass(); + if (bmmClass != null) { + bmmClass.setPrimitiveType(schema.getPrimitiveTypes().containsKey (bmmClass.getName())); + model.addClassDefinition(bmmClass, bmmPackage); + } + } + }); + pBmmPackage.getPackages().forEach( (subPackageName, pBmmSubPackage) -> { + convertPackage(model, schema, packageName + "." + pBmmSubPackage.getName(), pBmmSubPackage, bmmPackage); + }); + + } + private BmmPackage createBmmPackageDefinition(PBmmPackage p, PBmmPackage parent, BmmPackage parentPackageDefinition) { BmmPackage bmmPackageDefinition = new BmmPackage(p.getName()); diff --git a/bmm/src/main/java/org/openehr/bmm/v2/validation/validators/PropertyValidator.java b/bmm/src/main/java/org/openehr/bmm/v2/validation/validators/PropertyValidator.java index b3dbd019f..8856585ce 100644 --- a/bmm/src/main/java/org/openehr/bmm/v2/validation/validators/PropertyValidator.java +++ b/bmm/src/main/java/org/openehr/bmm/v2/validation/validators/PropertyValidator.java @@ -51,7 +51,7 @@ private void validateGenericProperty(PBmmClass pBmmClass, PBmmProperty pBmmPr attributeTypeDefinition.getRootType()); } - for(PBmmType genericParameter:attributeTypeDefinition.getGenericParameterDefs().values()) { + for(PBmmType genericParameter:attributeTypeDefinition.getGenericParameterRefs()) { validateGenericTypeDefParameter(pBmmClass, pBmmProperty, attributeTypeDefinition, genericParameter); } } else { diff --git a/json-schema/README.md b/json-schema/README.md new file mode 100644 index 000000000..e9ea46239 --- /dev/null +++ b/json-schema/README.md @@ -0,0 +1,12 @@ +#JSON Schema and OpenAPI generators + +This directory contains generation tasks for OpenEHR JSON Schema and OpenAPI models. + +To generate, go to this directory in the commandline and run: + +```shell script +../gradlew clean generateJsonSchema generateOpenAPI +``` + +The build directory will contain the output, which is currently a JSON Schema for RM 1.0.3, 1.0.4 and 1.1.0, and an OpenAPI +file containing the models of RM 1.1.0, plus one example API to make it a valid file. \ No newline at end of file diff --git a/json-schema/build.gradle b/json-schema/build.gradle new file mode 100644 index 000000000..b4f459efc --- /dev/null +++ b/json-schema/build.gradle @@ -0,0 +1,171 @@ +description = "tools that operate on the archie reference models and archetype object model" + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.nedap.healthcare.archie:archie-all:0.19.0' + } +} + +import com.nedap.archie.json.JSONSchemaCreator; +import com.nedap.archie.json.JsonSchemaUriProvider; +import com.nedap.archie.json.JsonSchemaUri; +import org.openehr.referencemodels.BuiltinReferenceModels; +import jakarta.json.*; +import jakarta.json.stream.JsonGenerator; +import org.openehr.bmm.core.BmmClass; +import com.nedap.archie.openapi.OpenAPIModelCreator; +import com.nedap.archie.json.flat.AttributeReference; + +class CustomUriProvider implements JsonSchemaUriProvider { + + private String baseUri; + public CustomUriProvider(String baseUri) { + this.baseUri = baseUri; + } + + public JsonSchemaUri provideJsonSchemaUrl(BmmClass bmmClass) { + return new JsonSchemaUri(baseUri, bmmClass.getPackagePath() + ".json"); + } +} + +abstract class JsonSchemaCreationTask extends DefaultTask { + + @OutputDirectory + abstract DirectoryProperty getOutputDir(); + + @TaskAction + def generateSchema() { + generate("singlefile", false) + generate("multifile", true) + } + + def generate(String dirName, boolean multifile) { + def rm110 = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.1.0").getModel(); + def rm104 = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.0.4").getModel(); + def rm103 = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.0.3").getModel(); + + def schemas110 = new JSONSchemaCreator() + .withBaseUri("https://specifications.openehr.org/releases/ITS-JSON/latest/components/RM/Release-1.1.0/") + .splitInMultipleFiles(multifile).create(rm110); + def schemas104 = new JSONSchemaCreator() + .withBaseUri("https://specifications.openehr.org/releases/ITS-JSON/latest/components/RM/Release-1.0.4/") + .splitInMultipleFiles(multifile).create(rm104); + def schemas103 = new JSONSchemaCreator() + .withBaseUri("https://specifications.openehr.org/releases/ITS-JSON/latest/components/RM/Release-1.0.3/") + .splitInMultipleFiles(multifile).create(rm103); + def config = new HashMap<>(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + def jsonWriterFactory = Json.createWriterFactory(config); + + printSchemas("RM-1.1.0", schemas110, jsonWriterFactory); + printSchemas("RM-1.0.4", schemas104, jsonWriterFactory); + printSchemas("RM-1.0.3", schemas103, jsonWriterFactory); + + } + def printSchemas(String version, Map schemas, JsonWriterFactory jsonWriterFactory) { + for(JsonSchemaUri name:schemas.keySet()) { + def schema = schemas.get(name); + def versionDir = getOutputDir().get().dir(version) + if(!versionDir.getAsFile().exists()) { + versionDir.getAsFile().mkdir(); + } + // def directory = versionDir.dir(outputDir) + // if(!directory.getAsFile().exists()) { + // directory.getAsFile().mkdir(); + // } + versionDir.file(name.getFilename()).getAsFile().withWriter { writer -> + jsonWriterFactory.createWriter(writer).write(schema); + } + } + } +} + +abstract class OpenAPICreationTask extends DefaultTask { + + @OutputDirectory + abstract DirectoryProperty getOutputDir(); + + @TaskAction + def generateOpenAPI () { + def rm110 = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.1.0").getModel(); + + OpenAPIModelCreator openAPIModelCreator = new OpenAPIModelCreator() + .allowAdditionalProperties(true); + //.withTypePropertyName("@type"); + + //let's do some annoying polymorphism mapping! + Set ignoredAttributes = new HashSet<>(); + //abstract any item. Not useful at all. + ignoredAttributes.add(new AttributeReference("EXTRACT_CONTENT_ITEM", "item")); + //generics redefinition. No generics support, so just DV_INTERVAL here. + ignoredAttributes.add(new AttributeReference("DV_QUANTITY", "normal_range")); + ignoredAttributes.add(new AttributeReference("DV_PROPORTION", "normal_range")); + ignoredAttributes.add(new AttributeReference("DV_COUNT", "normal_range")); + //same generics redefinition + ignoredAttributes.add(new AttributeReference("DV_QUANTITY", "other_reference_ranges")); + ignoredAttributes.add(new AttributeReference("DV_PROPORTION", "other_reference_ranges")); + ignoredAttributes.add(new AttributeReference("DV_COUNT", "other_reference_ranges")); + //uid is always the same except in extract. Let's keep it the same everywhere. + ignoredAttributes.add(new AttributeReference("EXTRACT_REQUEST", "uid")); + ignoredAttributes.add(new AttributeReference("PARTY", "uid")); + ignoredAttributes.add(new AttributeReference("EXTRACT_ACTION_REQUEST", "uid")); + //this makes id more specific, but objectref is also concrete, so let's not do that. + ignoredAttributes.add(new AttributeReference("LOCATABLE_REF", "id")); + + //accuracy: redefined. Ignore, define in concrete types instead + //keep DV_AMOUNT and DV_TEMPORAL here + ignoredAttributes.add(new AttributeReference("DV_QUANTIFIED", "accuracy")); + ignoredAttributes.add(new AttributeReference("DV_ABSOLUTE_QUANTITY", "accuracy")); + + List attributesFromParent = new ArrayList<>(); + openAPIModelCreator.setIgnoredAttributes(ignoredAttributes); + + openAPIModelCreator.setAddAttributesFromParent(attributesFromParent); + JsonObject jsonObject = openAPIModelCreator.create(rm110); + + def config = new HashMap<>(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + def jsonWriterFactory = Json.createWriterFactory(config); + + printOpenAPI("RM-1.1.0", "openAPI_RM_1.1.0.json", jsonObject, jsonWriterFactory); + } + + def printOpenAPI(String version, String fileName, JsonObject schema, JsonWriterFactory jsonWriterFactory) { + + def versionDir = getOutputDir().get().dir(version) + if(!versionDir.getAsFile().exists()) { + versionDir.getAsFile().mkdir(); + } + // def directory = versionDir.dir(outputDir) + // if(!directory.getAsFile().exists()) { + // directory.getAsFile().mkdir(); + // } + versionDir.file(fileName).getAsFile().withWriter { writer -> + jsonWriterFactory.createWriter(writer).write(schema); + } + + } + +} + + +tasks.register("clean") { + doLast { + delete rootProject.buildDir + } +} + +// Create a task using the task type +tasks.register('generateJsonSchema', JsonSchemaCreationTask) { + outputDir = file(layout.buildDirectory.dir('schemaOutput')) +} + +// Create a task using the task type +tasks.register('generateOpenAPI', OpenAPICreationTask) { + outputDir = file(layout.buildDirectory.dir('openAPI')) +} + + diff --git a/json-schema/gradle/wrapper/gradle-wrapper.jar b/json-schema/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..f3d88b1c2 Binary files /dev/null and b/json-schema/gradle/wrapper/gradle-wrapper.jar differ diff --git a/json-schema/gradle/wrapper/gradle-wrapper.properties b/json-schema/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..c5688696b --- /dev/null +++ b/json-schema/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jun 16 12:40:00 CEST 2021 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/json-schema/gradlew b/json-schema/gradlew new file mode 100755 index 000000000..2fe81a7d9 --- /dev/null +++ b/json-schema/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/json-schema/gradlew.bat b/json-schema/gradlew.bat new file mode 100644 index 000000000..24467a141 --- /dev/null +++ b/json-schema/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/json-schema/settings.gradle b/json-schema/settings.gradle new file mode 100644 index 000000000..88867f2f6 --- /dev/null +++ b/json-schema/settings.gradle @@ -0,0 +1 @@ +includeBuild("../") \ No newline at end of file diff --git a/referencemodels/src/main/resources/bmm/openEHR/components/BASE/Release-1.1.0/openehr_base_110.bmm b/referencemodels/src/main/resources/bmm/openEHR/components/BASE/Release-1.1.0/openehr_base_110.bmm index b9b68ee78..72da1c20c 100644 --- a/referencemodels/src/main/resources/bmm/openEHR/components/BASE/Release-1.1.0/openehr_base_110.bmm +++ b/referencemodels/src/main/resources/bmm/openEHR/components/BASE/Release-1.1.0/openehr_base_110.bmm @@ -468,7 +468,6 @@ class_definitions = < ["namespace"] = (P_BMM_SINGLE_PROPERTY) < name = <"namespace"> type = <"String"> - is_mandatory = > ["rm_publisher"] = (P_BMM_SINGLE_PROPERTY) < name = <"rm_publisher"> diff --git a/referencemodels/src/test/java/org/openehr/referencemodels/BmmComparison.java b/referencemodels/src/test/java/org/openehr/referencemodels/BmmComparison.java index c07ed5d5c..c21adf925 100644 --- a/referencemodels/src/test/java/org/openehr/referencemodels/BmmComparison.java +++ b/referencemodels/src/test/java/org/openehr/referencemodels/BmmComparison.java @@ -200,6 +200,12 @@ private String getBmmTypeName(BmmType type) { return getBmmTypeName(containerType.getBaseType()); } else if (type instanceof BmmGenericType) { BmmGenericType genericType = (BmmGenericType) type; + if ( genericType.getBaseClass().getType().getTypeName().toLowerCase().startsWith("hash") ) { + if(genericType.getGenericParameters().size() >= 2) { + //compare the second parameter here only + return getBmmTypeName(genericType.getGenericParameters().get(1)); + } + } return genericType.getBaseClass().getType().getTypeName(); } else if (type instanceof BmmParameterType) { BmmParameterType parameterType = (BmmParameterType) type; diff --git a/referencemodels/src/test/java/org/openehr/referencemodels/RMComparedWithBmmTest.java b/referencemodels/src/test/java/org/openehr/referencemodels/RMComparedWithBmmTest.java index a37f125e7..2198fd2a6 100644 --- a/referencemodels/src/test/java/org/openehr/referencemodels/RMComparedWithBmmTest.java +++ b/referencemodels/src/test/java/org/openehr/referencemodels/RMComparedWithBmmTest.java @@ -118,7 +118,7 @@ public void compareBmmWithRM() { knownDifferences.add(new ModelDifference(ModelDifferenceType.PROPERTY_MISSING_IN_BMM, "", "ARCHETYPE_HRID", "minor_version")); knownDifferences.add(new ModelDifference(ModelDifferenceType.PROPERTY_MISSING_IN_BMM, "", "ARCHETYPE_HRID", "patch_version")); knownDifferences.add(new ModelDifference(ModelDifferenceType.CARDINALITY_DIFFERENCE, "", "AUTHORED_RESOURCE", "translations")); - knownDifferences.add(new ModelDifference(ModelDifferenceType.TYPE_NAME_DIFFERENCE, "", "AUTHORED_RESOURCE", "translations")); + knownDifferences.add(new ModelDifference(ModelDifferenceType.PROPERTY_MISSING_IN_BMM, "", "DV_ABSOLUTE_QUANTITY", "magnitude")); knownDifferences.add(new ModelDifference(ModelDifferenceType.PROPERTY_MISSING_IN_BMM, "", "DV_AMOUNT", "magnitude")); knownDifferences.add(new ModelDifference(ModelDifferenceType.PROPERTY_MISSING_IN_BMM, "", "DV_DATE", "magnitude")); diff --git a/tools/src/main/java/com/nedap/archie/json/JSONSchemaCreator.java b/tools/src/main/java/com/nedap/archie/json/JSONSchemaCreator.java index ed45a294f..e1a8f9dec 100644 --- a/tools/src/main/java/com/nedap/archie/json/JSONSchemaCreator.java +++ b/tools/src/main/java/com/nedap/archie/json/JSONSchemaCreator.java @@ -10,21 +10,30 @@ import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.stream.JsonGenerator; + import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; public class JSONSchemaCreator { - + public static final String JSON_SCHEMA_FILE_EXTENSION = ".json"; private Map> primitiveTypeMapping; private List rootTypes; private BmmModel bmmModel; private final JsonBuilderFactory jsonFactory; + /** + * Whether to allow any additional properties in the openEHR json. + */ private boolean allowAdditionalProperties; + private boolean splitInPackages = false; + + private JsonSchemaUriProvider uriProvider = this::getSingleFileName; + private String baseUri = "https://specifications.openehr.org/releases/ITS-JSON/latest/components/RM/Release-1.1.0/"; public JSONSchemaCreator() { @@ -63,18 +72,32 @@ public JSONSchemaCreator() { rootTypes.add("ORGANISATION"); rootTypes.add("PARTY_IDENTITY"); rootTypes.add("ITEM_TREE"); + rootTypes.add("CONTRIBUTION"); + rootTypes.add("EHR"); + rootTypes.add("EHR_STATUS"); + rootTypes.add("ORIGINAL_VERSION"); + rootTypes.add("IMPORTED_VERSION"); Map config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); jsonFactory = Json.createBuilderFactory(config); } - public JsonObject create(BmmModel bmm) { + /** + * Creates one or more JSON Schemas from a BMM model. Depending on configuration, creates a file per BMM source schema id + * , package, or just one file. + * the main schema, containing the entry point, will always be the first file in the returned list + * + * @param bmm the BMM model to create the schema for + * @return a map, keyed by file name, of JsonObjects containing the json schemas + */ + public Map create(BmmModel bmm) { this.bmmModel = bmm; - + JsonSchemaUri mainFileName = new JsonSchemaUri(baseUri, this.splitInPackages ? + bmm.getSchemaId() + JSON_SCHEMA_FILE_EXTENSION : + bmm.getSchemaId() + "_all" + JSON_SCHEMA_FILE_EXTENSION); //create the definitions and the root if/else base JsonArrayBuilder allOfArray = jsonFactory.createArrayBuilder(); - JsonObjectBuilder definitions = jsonFactory.createObjectBuilder(); allOfArray.add(createRequiredArray("_type")); @@ -86,7 +109,7 @@ public JsonObject create(BmmModel bmm) { JsonObjectBuilder typePropertyCheck = createConstType(rootType); JsonObjectBuilder typeCheck = jsonFactory.createObjectBuilder().add("properties", typePropertyCheck); - JsonObjectBuilder typeReference = createReference(rootType); + JsonObjectBuilder typeReference = createRootlevelReference(mainFileName, rootType); //IF the type matches //THEN check the correct type from the definitions JsonObjectBuilder ifObject = jsonFactory.createObjectBuilder() @@ -94,19 +117,42 @@ public JsonObject create(BmmModel bmm) { .add("then", typeReference); allOfArray.add(ifObject); } + + Map schemas = new LinkedHashMap<>(); + + SchemaBuilder mainSchemaBuilder = new SchemaBuilder(mainFileName); + schemas.put(mainFileName, mainSchemaBuilder); + mainSchemaBuilder.getSchema().add("allOf", allOfArray); + for(BmmClass bmmClass: bmm.getClassDefinitions().values()) { if (!bmmClass.isAbstract() && !primitiveTypeMapping.containsKey(bmmClass.getName().toLowerCase())) { - addClass(definitions, bmmClass); + SchemaBuilder schema = getOrCreateDefinitions(schemas, bmmClass); + schema.getDefinitions().add(BmmDefinitions.typeNameToClassKey(bmmClass.getName()), createClass(bmmClass)); } } - return jsonFactory.createObjectBuilder() - .add("$schema", "http://json-schema.org/draft-07/schema") - .add("allOf", allOfArray) - .add("definitions", definitions) - .build(); + + Map result = new LinkedHashMap<>(); + //put the main schema first + for(SchemaBuilder schema:schemas.values()) { + result.put(schema.getUri(), schema.build()); + } + + return result; } - private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { + private SchemaBuilder getOrCreateDefinitions(Map result, BmmClass bmmClass) { + JsonSchemaUri uri = uriProvider.provideJsonSchemaUrl(bmmClass); + SchemaBuilder schemaBuilder = result.get(uri); + if(schemaBuilder == null) { + //file not yet found. Create it + schemaBuilder = new SchemaBuilder(uri); + result.put(uri, schemaBuilder); + } + return schemaBuilder; + + } + + private JsonObjectBuilder createClass(BmmClass bmmClass) { String bmmClassName = bmmClass.getName(); String typeName = BmmDefinitions.typeNameToClassKey(bmmClassName); @@ -122,7 +168,7 @@ private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { } else if((typeName.equalsIgnoreCase("POINT_EVENT") || typeName.equalsIgnoreCase("INTERVAL_EVENT")) && propertyName.equalsIgnoreCase("data")) { //we don't handle generics yet, and it's very tricky with the current BMM indeed. So, just manually hack this - JsonObjectBuilder propertyDef = createPolymorphicReference(bmmModel.getClassDefinition("ITEM_STRUCTURE")); + JsonObjectBuilder propertyDef = createPolymorphicReference(bmmClass, bmmModel.getClassDefinition("ITEM_STRUCTURE")); extendPropertyDef(propertyDef, bmmProperty); properties.add(propertyName, propertyDef); @@ -131,12 +177,12 @@ private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { } atLeastOneProperty = true; } else if ((typeName.equalsIgnoreCase("DV_URI") || typeName.equalsIgnoreCase("DV_EHR_URI")) && propertyName.equalsIgnoreCase("value")) { - JsonObjectBuilder propertyDef = createPropertyDef(bmmProperty.getType()); + JsonObjectBuilder propertyDef = createPropertyDef(bmmClass, bmmProperty.getType()); propertyDef.add("format", "uri-reference"); properties.add(propertyName, propertyDef); atLeastOneProperty = true; } else { - JsonObjectBuilder propertyDef = createPropertyDef(bmmProperty.getType()); + JsonObjectBuilder propertyDef = createPropertyDef(bmmClass, bmmProperty.getType()); extendPropertyDef(propertyDef, bmmProperty); properties.add(propertyName, propertyDef); @@ -147,7 +193,7 @@ private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { } } - properties.add("_type", jsonFactory.createObjectBuilder().add("type", "string").add("pattern", "^" + typeName + "(<.*>)?$")); + properties.add("_type", jsonFactory.createObjectBuilder().add("type", "string").add("pattern", "^" + typeName )); JsonObjectBuilder definition = jsonFactory.createObjectBuilder() .add("type", "object") .add("required", required) @@ -160,7 +206,7 @@ private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { if(!allowAdditionalProperties && atLeastOneProperty) { definition.add("additionalProperties", false); } - definitions.add(typeName, definition); + return definition; } private void extendPropertyDef(JsonObjectBuilder propertyDef, BmmProperty bmmProperty) { @@ -175,7 +221,7 @@ private void extendPropertyDef(JsonObjectBuilder propertyDef, BmmProperty bmm } } - private JsonObjectBuilder createPropertyDef(BmmType type) { + private JsonObjectBuilder createPropertyDef(BmmClass classContainingProperty, BmmType type) { if (type instanceof BmmParameterType) { return createType("object"); @@ -185,7 +231,7 @@ private JsonObjectBuilder createPropertyDef(BmmType type) { if (isJSPrimitive(type)) { return getJSPrimitive(simpleType); } else { - return createPolymorphicReference(simpleType.getBaseClass()); + return createPolymorphicReference(classContainingProperty, simpleType.getBaseClass()); } } else if (type instanceof BmmContainerType) { BmmContainerType containerType = (BmmContainerType) type; @@ -197,13 +243,13 @@ private JsonObjectBuilder createPropertyDef(BmmType type) { } return jsonFactory.createObjectBuilder() .add("type", "array") - .add("items", createPropertyDef(containerType.getBaseType())); + .add("items", createPropertyDef(classContainingProperty, containerType.getBaseType())); } else if (type instanceof BmmGenericType) { BmmGenericType genericType = (BmmGenericType) type; if (isJSPrimitive(genericType)) { return getJSPrimitive(genericType); } else { - return createPolymorphicReference(genericType.getBaseClass()); + return createPolymorphicReference(classContainingProperty, genericType.getBaseClass()); } } @@ -213,10 +259,11 @@ private JsonObjectBuilder createPropertyDef(BmmType type) { /** * Create a reference to a given type, plus all its descendants. + * @param classContainingTypeReference the class containing the type reference - used to check to refer to just #definitions, or prepended with a filename * @param type the type to refer to * @return the json schema that is a reference to this type, plus all of its descendants */ - private JsonObjectBuilder createPolymorphicReference(BmmClass type) { + private JsonObjectBuilder createPolymorphicReference(BmmClass classContainingTypeReference, BmmClass type) { List descendants = getAllNonAbstractDescendants( type); //if the type to refer to is abstract, a _type field is required, because there is no class to fall back on @@ -272,7 +319,7 @@ private JsonObjectBuilder createPolymorphicReference(BmmClass type) { typeCheck.addAll(createRequiredArray("_type")); } - JsonObjectBuilder typeReference = createReference(descendant); + JsonObjectBuilder typeReference = createReference(classContainingTypeReference, descendant); //IF the type matches //THEN check the correct type from the definitions JsonObjectBuilder ifObject = jsonFactory.createObjectBuilder() @@ -286,13 +333,13 @@ private JsonObjectBuilder createPolymorphicReference(BmmClass type) { //fallback to the base type if it is concrete, and not if it is abstract JsonObjectBuilder elseObject = jsonFactory.createObjectBuilder() .add("if", jsonFactory.createObjectBuilder().add("not", createRequiredArray("_type"))) - .add("then", createReference(type.getName())); + .add("then", createReference(classContainingTypeReference, type.getName())); array.add(elseObject); } return jsonFactory.createObjectBuilder().add("allOf", array); } else { - return createReference(descendants.get(0)); + return createReference(classContainingTypeReference, descendants.get(0)); } } @@ -327,24 +374,12 @@ private JsonObjectBuilder getJSPrimitive(String classKey) { private JsonObjectBuilder createConstType(String rootType) { - boolean generic = false; - BmmClass classDefinition = bmmModel.getClassDefinition(rootType); + return jsonFactory.createObjectBuilder() + .add("_type", jsonFactory.createObjectBuilder() + .add("const", rootType) + ); - if(classDefinition == null || classDefinition instanceof BmmGenericClass) { - generic = true; - } - if(generic) { - return jsonFactory.createObjectBuilder() - .add("_type", jsonFactory.createObjectBuilder() - .add("type", "string").add("pattern", "^" + rootType + "<.*>$") - ); - } else { - return jsonFactory.createObjectBuilder() - .add("_type", jsonFactory.createObjectBuilder() - .add("const", rootType) - ); - } } private JsonObjectBuilder createRequiredArray(String... requiredFields) { @@ -360,12 +395,95 @@ private JsonObjectBuilder createType(String jsPrimitive) { return jsonFactory.createObjectBuilder().add("type", jsPrimitive); } - private JsonObjectBuilder createReference(String rootType) { - return jsonFactory.createObjectBuilder().add("$ref", "#/definitions/" + rootType); + private JsonObjectBuilder createRootlevelReference(JsonSchemaUri packageFileName, String type) { + BmmClass bmmClass = bmmModel.getClassDefinitions().get(type); + if(bmmClass == null) { + //TODO: a warning! + return jsonFactory.createObjectBuilder().add("$ref", "#/definitions/" + type); + } else { + JsonSchemaUri typeFileName = uriProvider.provideJsonSchemaUrl(bmmClass); + if(typeFileName == null) { + throw new RuntimeException(); + } + if(typeFileName.equals(packageFileName)) { + return jsonFactory.createObjectBuilder().add("$ref", "#/definitions/" + type); + } else { + if(typeFileName.getBaseUri().equals(packageFileName.getBaseUri())) { + return jsonFactory.createObjectBuilder().add("$ref", typeFileName.getFilename() + "#/definitions/" + type); + } else { + return jsonFactory.createObjectBuilder().add("$ref", typeFileName.getId() + "#/definitions/" + type); + } + } + } + + } + + + private JsonObjectBuilder createReference(BmmClass classContainingReference, String type) { + JsonSchemaUri packageFileName = classContainingReference == null ? new JsonSchemaUri("", "") : uriProvider.provideJsonSchemaUrl(classContainingReference); + return createRootlevelReference(packageFileName, type); } public JSONSchemaCreator allowAdditionalProperties(boolean allowAdditionalProperties) { this.allowAdditionalProperties = allowAdditionalProperties; return this; } + + public JSONSchemaCreator splitInMultipleFiles(boolean splitInPackages) { + this.splitInPackages = splitInPackages; + if(splitInPackages) { + this.uriProvider = this::getBmmFileName; + } else { + this.uriProvider = this::getSingleFileName; + } + return this; + } + + public JSONSchemaCreator withJsonSchemaUriProvider(JsonSchemaUriProvider uriProvider) { + this.uriProvider = uriProvider; + return this; + } + + public JSONSchemaCreator withBaseUri(String baseUri) { + this.baseUri = baseUri; + return this; + } + + private JsonSchemaUri getSingleFileName(BmmClass clazz) { + return new JsonSchemaUri(baseUri, clazz.getBmmModel().getSchemaId() + "_all" + JSON_SCHEMA_FILE_EXTENSION); + } + + private JsonSchemaUri getBmmFileName(BmmClass clazz) { + return new JsonSchemaUri(baseUri, clazz.getSourceSchemaId() + JSON_SCHEMA_FILE_EXTENSION); + } + + private class SchemaBuilder { + private JsonSchemaUri uri; + private JsonObjectBuilder schema; + private JsonObjectBuilder definitions; + + public SchemaBuilder(JsonSchemaUri uri) { + this.uri = uri; + definitions = jsonFactory.createObjectBuilder(); + schema = jsonFactory.createObjectBuilder() + .add("$schema", "http://json-schema.org/draft-07/schema") + .add("$id", uri.getId()); + } + + public JsonSchemaUri getUri() { + return uri; + } + + public JsonObjectBuilder getSchema() { + return schema; + } + public JsonObjectBuilder getDefinitions() { + return definitions; + } + + public JsonObject build() { + return schema.add("definitions", definitions).build(); + } + + } } diff --git a/tools/src/main/java/com/nedap/archie/json/JsonSchemaUri.java b/tools/src/main/java/com/nedap/archie/json/JsonSchemaUri.java new file mode 100644 index 000000000..4f4856364 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/json/JsonSchemaUri.java @@ -0,0 +1,48 @@ +package com.nedap.archie.json; + +import java.util.Objects; + +public class JsonSchemaUri { + + // the value of the $id property + private String baseUri; + // the filename to be used in ref + private String filename; + + public JsonSchemaUri(String baseUri, String filename) { + this.baseUri = baseUri; + this.filename = filename; + } + + public String getBaseUri() { + return baseUri; + } + + public String getFilename() { + return filename; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + JsonSchemaUri that = (JsonSchemaUri) o; + return Objects.equals(baseUri, that.baseUri) && + Objects.equals(filename, that.filename); + } + + @Override + public int hashCode() { + return Objects.hash(baseUri, filename); + } + + public String getId() { + return baseUri + filename; + } + + + @Override + public String toString() { + return getId(); + } +} diff --git a/tools/src/main/java/com/nedap/archie/json/JsonSchemaUriProvider.java b/tools/src/main/java/com/nedap/archie/json/JsonSchemaUriProvider.java new file mode 100644 index 000000000..b070f0675 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/json/JsonSchemaUriProvider.java @@ -0,0 +1,12 @@ +package com.nedap.archie.json; + +import org.openehr.bmm.core.BmmClass; + +/** + * Provides a URL for a given JSON schema + */ +@FunctionalInterface +public interface JsonSchemaUriProvider { + + JsonSchemaUri provideJsonSchemaUrl(BmmClass clazz); +} diff --git a/tools/src/main/java/com/nedap/archie/json/JsonSchemaValidator.java b/tools/src/main/java/com/nedap/archie/json/JsonSchemaValidator.java index 328b97906..db6f2e596 100644 --- a/tools/src/main/java/com/nedap/archie/json/JsonSchemaValidator.java +++ b/tools/src/main/java/com/nedap/archie/json/JsonSchemaValidator.java @@ -2,6 +2,8 @@ import com.google.common.base.Charsets; import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonSchemaReader; +import org.leadpony.justify.api.JsonSchemaReaderFactory; import org.leadpony.justify.api.JsonValidationService; import org.leadpony.justify.api.Problem; import org.leadpony.justify.api.ProblemHandler; @@ -12,8 +14,12 @@ import jakarta.json.JsonStructure; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.net.URI; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** @@ -27,8 +33,16 @@ */ public class JsonSchemaValidator { + /** The generated json schema files, in memory */ + private final Map schemaFiles; + /** a cache of earlier resolved schemas, to not cause too many performance problems */ + private final Map resolvedSchemas = new LinkedHashMap<>(); + /** the single resolved schema */ JsonSchema schema; + private JsonSchemaReaderFactory readerFactory; + private JsonValidationService service; + /** * Creates a JsonSchemaValidator that validates against the json schema created from the given Bmm Model * The JSON Schema complies to the JSON format that the OpenEHR project uses. This may very well be different from @@ -38,13 +52,64 @@ public class JsonSchemaValidator { * @param allowAdditionalProperties whether to allow additional properties in the JSON */ public JsonSchemaValidator(BmmModel bmmModel, boolean allowAdditionalProperties) { - JsonObject schemaJson = new JSONSchemaCreator().allowAdditionalProperties(allowAdditionalProperties).create(bmmModel); + schemaFiles = new LinkedHashMap<>(); + new JSONSchemaCreator() + .allowAdditionalProperties(allowAdditionalProperties) + //the validator can actually handle a schema split in multiple files, but + //Justify's implementation is not perfect, causing some extra memory use that might be better to avoid. + .splitInMultipleFiles(false) + .create(bmmModel) + .forEach( (uri, schema) -> schemaFiles.put(uri.getId(), schema)); + //The first entry in schemaFiles is guaranteed to be the main schema by the JSONSchemaCreator. + JsonObject schemaJson = schemaFiles.values().iterator().next(); + + + service = JsonValidationService.newInstance(); + //the following is for multi-file validation only, and can be skipped altogether to validate just a single-file + //schema + readerFactory = service + .createSchemaReaderFactoryBuilder() + .withSchemaResolver(this::resolveSchema) + .build(); + + try (JsonSchemaReader schemaReader = readerFactory + .createSchemaReader(createByteArrayInputStream(schemaJson.toString()))) { + schema = schemaReader.read(); + } + + } + - JsonValidationService service = JsonValidationService.newInstance(); - schema = service.readSchema(createByteArrayInputStream(schemaJson.toString())); + /** + * Resolves the referenced JSON schema. + * + * @param uri the identifier of the referenced JSON schema. + * @return referenced JSON schema. + */ + private JsonSchema resolveSchema(URI uri) { + + String filename = uri.toString().replaceAll("#", ""); + JsonSchema resolvedSchema = resolvedSchemas.get(filename); + if(resolvedSchema != null) { + return resolvedSchema; + } + JsonObject schema = schemaFiles.get(filename); + if ( schema == null ) { + return null; + } + try (JsonSchemaReader reader = readerFactory.createSchemaReader(createByteArrayInputStream(schema.toString()))) { + resolvedSchema = reader.read(); + //this caching approach is not perfect: as part of reader.read(), it will call this same function again to + //resolve any referenced schemas, before returning frmo reader.read(). + //this can mean the result is not yet cached, and will be generated twice. Not something that can be easily fixed + // however if no cache is used at all, there will be OutOfMemoryExceptions or very long runtime. + resolvedSchemas.put(filename, resolvedSchema); + return resolvedSchema; + } } + private ByteArrayInputStream createByteArrayInputStream(String json) { return new ByteArrayInputStream(json.getBytes(Charsets.UTF_8)); } @@ -57,7 +122,6 @@ private ByteArrayInputStream createByteArrayInputStream(String json) { */ public List validate(String json) throws IOException { - JsonValidationService service = JsonValidationService.newInstance(); List allProblems = new ArrayList<>(); ProblemHandler problemHandler = new ProblemHandler() { @Override diff --git a/tools/src/main/java/com/nedap/archie/json/flat/AttributeReference.java b/tools/src/main/java/com/nedap/archie/json/flat/AttributeReference.java index fe3763bea..56d90b3bd 100644 --- a/tools/src/main/java/com/nedap/archie/json/flat/AttributeReference.java +++ b/tools/src/main/java/com/nedap/archie/json/flat/AttributeReference.java @@ -60,4 +60,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(typeName, attributeName); } + + @Override + public String toString() { + return typeName + "." + attributeName; + } } diff --git a/tools/src/main/java/com/nedap/archie/openapi/ModelInfoLookupToPBmmConverter.java b/tools/src/main/java/com/nedap/archie/openapi/ModelInfoLookupToPBmmConverter.java new file mode 100644 index 000000000..74effed8f --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/openapi/ModelInfoLookupToPBmmConverter.java @@ -0,0 +1,309 @@ +package com.nedap.archie.openapi; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.Lists; +import com.nedap.archie.base.Interval; +import com.nedap.archie.json.flat.AttributeReference; +import com.nedap.archie.rminfo.ModelInfoLookup; +import com.nedap.archie.rminfo.RMAttributeInfo; +import com.nedap.archie.rminfo.RMTypeInfo; +import org.openehr.bmm.core.BmmModel; +import org.openehr.bmm.v2.persistence.*; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class ModelInfoLookupToPBmmConverter { + + private ModelInfoLookup modelInfoLookup; + + private boolean excludedJsonIgnoreFields; + + public boolean isExcludedJsonIgnoreFields() { + return excludedJsonIgnoreFields; + } + + public void setExcludedJsonIgnoreFields(boolean excludedJsonIgnoreFields) { + this.excludedJsonIgnoreFields = excludedJsonIgnoreFields; + } + + public PBmmSchema convert(ModelInfoLookup lookup, Set ignoredAttributes, BmmModel includedModel) { + this.modelInfoLookup = lookup; + PBmmSchema schema = new PBmmSchema(); + PBmmPackage pack = new PBmmPackage("default"); + Map packageMap = new LinkedHashMap<>(); + packageMap.put("default", pack); + schema.setPackages(packageMap); + + List packageClasses = new ArrayList<>(); + + Map classDefinitions = new LinkedHashMap<>(); + + List foundEnums = new ArrayList<>(); + + for(RMTypeInfo type:lookup.getAllTypes()) { + if(includedModel != null && includedModel.getClassDefinition(convertTypeName(type.getRmName())) != null) { + System.out.println("already in base: " + type.getRmName()); + continue; + } + packageClasses.add(type.getRmName()); + PBmmClass bmmClass = new PBmmClass(); + bmmClass.setName(type.getRmName()); + boolean classIsAbstract = Modifier.isAbstract(type.getJavaClass().getModifiers()); + if(classIsAbstract) { + bmmClass.setAbstract(classIsAbstract); + } + bmmClass.setAncestors(type.getDirectParentClasses().stream() + .map(t -> convertTypeName(t.getRmName())).collect(Collectors.toList()) + ); + if(bmmClass.getAncestors().isEmpty()) { + bmmClass.setAncestors(Lists.newArrayList("Any")); + } + + Map> properties = new LinkedHashMap<>(); + for(RMAttributeInfo attribute:type.getAttributes().values()) { + if(attribute.isFromAncestor() || ignoredAttributes.contains(new AttributeReference(type.getRmName(), attribute.getRmName()))) { + continue; + } + if(hasJsonIgnoreAnnotation(attribute)) { + continue; + } + if(attribute.getType().isEnum()) { + foundEnums.add(attribute.getType()); + } + properties.put(attribute.getRmName(), createPBmmProperty(type, attribute)); + } + bmmClass.setProperties(properties); + classDefinitions.put(type.getRmName(), bmmClass); + } + + schema.setClassDefinitions(classDefinitions); + pack.setClasses(packageClasses); + + Map includes = new LinkedHashMap<>(); + BmmIncludeSpec include = new BmmIncludeSpec(); + include.setId("openehr_base_1.1.0"); + includes.put("1", include); + + schema.setIncludes(includes); + + setMetaInformation(schema); + + addEnums(foundEnums, pack, lookup, schema); + + return schema; + } + + private boolean hasJsonIgnoreAnnotation(RMAttributeInfo attribute) { + if(!this.excludedJsonIgnoreFields) { + return false; + } + + if(attribute.getGetMethod() != null) { + if(attribute.getGetMethod().getAnnotation(JsonIgnore.class) != null) { + return true; + } + } + if(attribute.getSetMethod() != null) { + if(attribute.getSetMethod().getAnnotation(JsonIgnore.class) != null) { + return true; + } + } + if(attribute.getField() != null) { + if(attribute.getField().getAnnotation(JsonIgnore.class) != null) { + return true; + } + } + return false; + } + + /** + * Add all found enums to the schema + * @param foundEnums all the enum classes found + * @param pack + * @param schema the P_BMM schema + */ + private void addEnums(List foundEnums, PBmmPackage pack, ModelInfoLookup lookup, PBmmSchema schema) { + for(Class clazz:foundEnums){ + System.out.println("enum"); + //this can be either a string or an integer, we really don't know based on just the enum + PBmmEnumerationString bmmEnum = new PBmmEnumerationString(); + bmmEnum.setAncestors(Lists.newArrayList("String")); + bmmEnum.setName(lookup.getNamingStrategy().getTypeName(clazz)); + bmmEnum.setItemNames(Arrays.stream(clazz.getEnumConstants()).map(t -> t.toString()).collect(Collectors.toList())); + pack.getClasses().add(bmmEnum.getName()); + schema.getClassDefinitions().put(bmmEnum.getName(), bmmEnum); + //["PROPORTION_KIND"] = (P_BMM_ENUMERATION_INTEGER) < + // name = <"PROPORTION_KIND"> + // ancestors = <"Integer"> + // item_names = <"pk_ratio", "pk_unitary", "pk_percent", "pk_fraction", "pk_integer_fraction"> + // > + } + + } + + private void setMetaInformation(PBmmSchema schema) { + schema.setBmmVersion("2.3"); + + schema.setRmPublisher("openehr"); + schema.setSchemaName("aom"); + schema.setRmRelease("2.3.0"); + + schema.setSchemaAuthor("auto-generated by archie"); + schema.setSchemaRevision("0.0.1"); + schema.setSchemaLifecycleState("auto-generated experiment"); + schema.setSchemaDescription("Archetype Object model, autogenerated as implemented in Archie"); + } + + private PBmmProperty createPBmmProperty(RMTypeInfo type, RMAttributeInfo attribute) { + String typeInCollection = convertType(type.getRmName(), attribute.getTypeNameInCollection()); + if(Map.class.isAssignableFrom(attribute.getType())) { + PBmmGenericProperty property = new PBmmGenericProperty(); + setBasicsForProperty(attribute, property); + PBmmGenericType pBmmType = createMapContainerType(attribute.getTypeInCollection(), (ParameterizedType) attribute.getGetMethod().getGenericReturnType(), typeInCollection); + property.setTypeDef(pBmmType); + return property; +// PBmmGenericProperty property = new PBmmGenericProperty(); +// property.setComputed(attribute.isComputed()); +// property.setName(attribute.getRmName()); +// property.setMandatory(!attribute.isNullable()); +// PBmmGenericType pBmmType = new PBmmGenericType(); +// pBmmType.setRootType("Hash"); +// property.setTypeDef(pBmmType); +// properties.put(attribute.getRmName(), property); + } else if(attribute.isMultipleValued()) { + + PBmmContainerProperty property = new PBmmContainerProperty(); + setBasicsForProperty(attribute, property); + //property.setCardinality(Interval.upperUnbounded(0, true)); + PBmmContainerType pBmmType = new PBmmContainerType(); + if(typeInCollection.startsWith("INTERVAL")) { + //generics! + pBmmType.setTypeDef(new PBmmGenericType(typeInCollection, new ArrayList<>()));//TODO: add generic parameters! + } else { + pBmmType.setType(typeInCollection); + } + pBmmType.setContainerType("List"); + property.setTypeDef(pBmmType); + return property; + } else { + PBmmSingleProperty property = new PBmmSingleProperty(); + setBasicsForProperty(attribute, property); + property.setType(typeInCollection); + return property; + } + } + + private PBmmGenericType createHashType(PBmmType valueType) { + PBmmGenericType pBmmType = new PBmmGenericType(); + pBmmType.setRootType("Hash"); + Map paramdefs = new LinkedHashMap<>(); + pBmmType.setGenericParameterDefs(paramdefs); + + paramdefs.put("K", new PBmmSimpleType("String")); + paramdefs.put("V", valueType); + return pBmmType; + } + + private PBmmGenericType createMapContainerType(Class classInCollection, ParameterizedType genericTypeOfCollection, String typeInCollectionName) { + + if(Map.class.isAssignableFrom(classInCollection)) { + PBmmType valueType = null; + + Type mapContent = genericTypeOfCollection.getActualTypeArguments()[1]; + if(mapContent instanceof ParameterizedType) { + ParameterizedType paramContent = (ParameterizedType) mapContent; + valueType = createMapContainerType((Class) paramContent.getRawType(), paramContent, "Hash"); + } else if(mapContent instanceof Class){ + valueType = new PBmmSimpleType(modelInfoLookup.getNamingStrategy().getTypeName((Class) mapContent)); + } else { + throw new UnsupportedOperationException("type with class " + mapContent.getClass().getName() + " Not implemented"); + } + return createHashType(valueType); +// } else if(Map.class.isAssignableFrom(classInCollection)) { +// PBmmContainerType pBmmType = new PBmmContainerType(); +// pBmmType.setContainerType("Hash"); +// //ok need a typeDef here +// //sorry about this construction +// //TODO: rewrite into a proper method that does proper bounds and type checks +// Type mapContent = genericTypeOfCollection.getActualTypeArguments()[1]; +// +// if(mapContent instanceof ParameterizedType) { +// ParameterizedType paramContent = (ParameterizedType) mapContent; +// pBmmType.setTypeDef(createMapContainerType((Class) paramContent.getRawType(), paramContent, "Hash")); +// } else { +// pBmmType.setType(mapContent.getTypeName()); +// } +// return pBmmType; +// } + } else { + return createHashType(new PBmmSimpleType(typeInCollectionName)); + } + + } + + private void setBasicsForProperty(RMAttributeInfo attribute, PBmmProperty property) { + if(attribute.isComputed()) { + property.setComputed(attribute.isComputed()); + } + property.setName(convertTypeName(attribute.getRmName())); + if(!attribute.isNullable()) { + property.setMandatory(!attribute.isNullable()); + } + } + + private String convertTypeName(String type) { + return convertType(null, type); + } + + private String convertType(String baseClass, String typeName) { + if(baseClass != null) { + switch (baseClass) { + case "C_DATE": + if (typeName.equalsIgnoreCase("TEMPORAL_ACCESSOR")) { + return "Date"; + } + break; + case "C_TIME": + if (typeName.equalsIgnoreCase("TEMPORAL_ACCESSOR")) { + return "Time"; + } + break; + case "C_DATE_TIME": + if (typeName.equalsIgnoreCase("TEMPORAL_ACCESSOR")) { + return "Date_time"; + } + break; + } + } + switch(typeName.toLowerCase()) { + case "double": + return "Real"; + case "long": + return "Integer"; + case "map": + return "Hash"; + case "temporal_accessor": + return "Iso8601_type"; //should have been caught above + case "temporal_amount": + return "Duration"; + case "string": + return "String"; + case "boolean": + return "Boolean"; + case "archetype_model_object": + case "object": + return "Any"; + default: + return typeName; + } + } +} diff --git a/tools/src/main/java/com/nedap/archie/openapi/OpenAPIModelCreator.java b/tools/src/main/java/com/nedap/archie/openapi/OpenAPIModelCreator.java new file mode 100644 index 000000000..15fe86a68 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/openapi/OpenAPIModelCreator.java @@ -0,0 +1,571 @@ +package com.nedap.archie.openapi; + + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.google.common.base.CaseFormat; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.nedap.archie.aom.profile.AomProfiles; +import com.nedap.archie.aom.terminology.ArchetypeTerm; +import com.nedap.archie.aom.utils.AOMUtils; +import com.nedap.archie.json.flat.AttributeReference; +import org.openehr.bmm.core.*; +import org.openehr.bmm.persistence.validation.BmmDefinitions; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.stream.JsonGenerator; +import org.threeten.extra.PeriodDuration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class OpenAPIModelCreator { + + + public static final PropertyNamingStrategy.UpperCamelCaseStrategy camelCaseStrategy = new PropertyNamingStrategy.UpperCamelCaseStrategy(); + private Map> primitiveTypeMapping; + private Set ignoredTypes; + private String baseType;//'any' + private List rootTypes; + private Map> ancestorOverrides; + private BmmModel bmmModel; + private final JsonBuilderFactory jsonFactory; + private String typeNameProperty = "_type"; + //whether to add oneOf to polymorphic references + private boolean writeOneOf = true; + private RedefinedProperties redefinedProperties; + + /** + * Properties that should not be output. + */ + private Set ignoredAttributes; + + /** + * Attributes that should be added from the parent, at the given class. + * So if this contains DV_QUANTITY.accuracy, accuracy will be added from the closest parent + * in the inheritance tree, to the definition of DV_QUANTITY. + * This map has classname as key, and propertyname as values + */ + private Map> addAttributesFromParent; + + private boolean allowAdditionalProperties; + private String exampleRootTypeName = "Composition"; + /** + *A set containing all the type names that should be exported flat, without any ancestry. + * Useful to map a type hierarchy that cannot be properly mapped to OpenAPI + */ + private HashSet ignoredAncestors; + private Map typeOverrides = new HashMap<>(); + + public OpenAPIModelCreator() { + primitiveTypeMapping = new HashMap<>(); + primitiveTypeMapping.put("integer", () -> createType("integer")); + primitiveTypeMapping.put("integer64", () -> createType("integer")); + primitiveTypeMapping.put("boolean", () -> createType("boolean")); + primitiveTypeMapping.put("real", () -> createType("number")); + primitiveTypeMapping.put("double", () -> createType("number")); + primitiveTypeMapping.put("octet", () -> createType("string"));//well, base64... + primitiveTypeMapping.put("byte", () -> createType("string")); + primitiveTypeMapping.put("character", () -> createType("string")); + primitiveTypeMapping.put("hash", () -> createType("object")); + primitiveTypeMapping.put("string", () -> createType("string")); + primitiveTypeMapping.put("date", () -> createType("string").add("format", "date")); + primitiveTypeMapping.put("date_time", () -> createType("string").add("format", "date_time")); + primitiveTypeMapping.put("time", () -> createType("string").add("format", "time")); + primitiveTypeMapping.put("duration", () -> createType("string")); + primitiveTypeMapping.put("iso8601_date", () -> createType("string").add("format", "date")); + primitiveTypeMapping.put("iso8601_date_time", () -> createType("string").add("format", "date-time")); + primitiveTypeMapping.put("iso8601_time", () -> createType("string").add("format", "time")); + primitiveTypeMapping.put("iso8601_duration", () -> createType("string")); + primitiveTypeMapping.put("proportion_kind", () -> createType("integer"));//TODO: proper enum support + primitiveTypeMapping.put("operator_kind", () -> createType("string"));//TODO: proper enum support + + rootTypes = new ArrayList<>(); + rootTypes.add("COMPOSITION"); + rootTypes.add("OBSERVATION"); + rootTypes.add("EVALUATION"); + rootTypes.add("ACTIVITY"); + rootTypes.add("ACTION"); + rootTypes.add("SECTION"); + rootTypes.add("INSTRUCTION"); + rootTypes.add("INSTRUCTION_DETAILS"); + rootTypes.add("ADMIN_ENTRY"); + rootTypes.add("CLUSTER"); + rootTypes.add("CAPABILITY"); + rootTypes.add("PERSON"); + rootTypes.add("ADDRESS"); + rootTypes.add("ROLE"); + rootTypes.add("ORGANISATION"); + rootTypes.add("PARTY_IDENTITY"); + rootTypes.add("ITEM_TREE"); + + ancestorOverrides = new LinkedHashMap<>(); + ancestorOverrides.put("DV_INTERVAL", Lists.newArrayList("DATA_VALUE", "INTERVAL")); + + baseType = "Any"; + ignoredTypes = Sets.newHashSet("ORDERED", "HASH", "CONTAINER", "SET", "ARRAY", "LIST", + "DATE_TIME", "DATE", "TIME", "ISO8601_TYPE", "TEMPORAL", "NUMERIC", "ORDERED_NUMERIC", + "RESOURCE_DESCRIPTION", "RESOURCE_DESCRIPTION_ITEM", "AUTHORED_RESOURCE", "TRANSLATION_DETAILS"); + + + addAttributesFromParent = new HashMap<>(); + ignoredAttributes = new HashSet<>(); + ignoredAncestors = new HashSet<>(); + Map config = new HashMap(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + jsonFactory = Json.createBuilderFactory(config); + } + + public void setIgnoredAttributes(Set ignoredAttributes) { + this.ignoredAttributes = ignoredAttributes; + } + + public void setAddAttributesFromParent(List attributesToAddFromParent) { + this.addAttributesFromParent = new HashMap<>(); + attributesToAddFromParent.forEach(ref -> { + Set attributes = addAttributesFromParent.get(ref.getTypeName()); + if(attributes == null) { + attributes = new HashSet<>(); + addAttributesFromParent.put(ref.getTypeName(), attributes); + } + attributes.add(ref.getAttributeName()); + + }); + } + + public JsonObject create(BmmModel bmm) { + this.bmmModel = bmm; + this.redefinedProperties = new RedefinedProperties(bmm); + + //create the definitions and the root if/else base + + JsonArrayBuilder allOfArray = jsonFactory.createArrayBuilder(); + JsonObjectBuilder definitions = jsonFactory.createObjectBuilder(); + + + allOfArray.add(createRequiredArray(typeNameProperty)); + + //for every root type, if the type is right, check that type + //anyof does more or less the same, but this is faster plus it gives MUCH less errors! +// for(String rootType:rootTypes) { +// +// JsonObjectBuilder typePropertyCheck = createConstType(rootType); +// JsonObjectBuilder typeCheck = jsonFactory.createObjectBuilder().add("properties", typePropertyCheck); +// +// JsonObjectBuilder typeReference = createReference(rootType); +// //IF the type matches +// //THEN check the correct type from the definitions +// JsonObjectBuilder ifObject = jsonFactory.createObjectBuilder() +// .add("if", typeCheck) +// .add("then", typeReference); +// allOfArray.add(ifObject); +// } + for(BmmClass bmmClass: bmm.getClassDefinitions().values()) { + if (shouldClassBeIncluded(bmmClass)) { + addClass(definitions, bmmClass); + } + } + return jsonFactory.createObjectBuilder() + .add("openapi", "3.0.1") +// openapi: 3.0.0 +// info: +// title: Sample API +// description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. +// version: 0.1.9 + .add("info", jsonFactory.createObjectBuilder() + .add("title", "OpenEHR API stub") + .add("description", "empty OpenAPI model file for RM 1.0.4") + .add("contact", jsonFactory.createObjectBuilder().add("name", "Pieter Bos").add("email", "pieter.bos@nedap.com")) + .add("version", "0.0.1") + ) + .add("paths", jsonFactory.createObjectBuilder() + .add("/something", jsonFactory.createObjectBuilder() + .add("get", jsonFactory.createObjectBuilder() + .add("summary", "something") + .add("description", "something describes") + .add("operationId", "getDescription") + .add("responses", jsonFactory.createObjectBuilder() + .add("default", jsonFactory.createObjectBuilder() + .add("description", "default") + .add("content", jsonFactory.createObjectBuilder() + .add("application/json", jsonFactory.createObjectBuilder() + .add("schema", createReference(exampleRootTypeName)) + ) + ) + ) + ) + ) + ) + ) + .add("components", + jsonFactory.createObjectBuilder().add("schemas", definitions) + ).build(); +// .add("$schema", "http://json-schema.org/draft-07/schema") +// .add("allOf", allOfArray) +// .add("definitions", definitions) +// .build(); + } + + private boolean shouldClassBeIncluded(BmmClass bmmClass) { + return !ignoredTypes.contains(BmmDefinitions.typeNameToClassKey(bmmClass.getName())) && !primitiveTypeMapping.containsKey(bmmClass.getName().toLowerCase()); + } + + private boolean shouldClassBeIncluded(String className) { + return !ignoredTypes.contains(BmmDefinitions.typeNameToClassKey(className)) && !primitiveTypeMapping.containsKey(className.toLowerCase()); + } + + private void addClass(JsonObjectBuilder definitions, BmmClass bmmClass) { + String bmmClassName = bmmClass.getName(); + String originalTypeName = BmmDefinitions.typeNameToClassKey(bmmClassName); + String typeName = convertTypeName(originalTypeName); + + BmmClass flatBmmClass = bmmClass; //NO flattening! + + JsonArrayBuilder required = jsonFactory.createArrayBuilder(); + JsonObjectBuilder properties = jsonFactory.createObjectBuilder(); + + boolean hasRequiredProperties = false; + boolean atLeastOneProperty = false; + Map> bmmProperties = flatBmmClass.getProperties(); + if(this.ignoredAncestors.contains(bmmClassName)) { + bmmProperties = bmmClass.getFlatProperties(); + } + for (String propertyName : bmmProperties.keySet()) { + BmmProperty bmmProperty = bmmProperties.get(propertyName); + if(bmmProperty.getComputed()) { + continue;//don't output this + } else if (ignoredAttributes.contains(new AttributeReference(bmmClassName, propertyName))) { + continue; + } else if((bmmClass.getName().startsWith("POINT_EVENT") || bmmClass.getName().startsWith("INTERVAL_EVENT")) && + propertyName.equalsIgnoreCase("data")) { + //we don't handle generics yet, and it's very tricky with the current BMM indeed. So, just manually hack this + JsonObjectBuilder propertyDef = createPolymorphicReference(bmmModel.getClassDefinition("ITEM_STRUCTURE")); + extendPropertyDef(propertyDef, bmmProperty); + properties.add(propertyName, propertyDef); + + if (bmmProperty.getMandatory()) { + required.add(propertyName); + hasRequiredProperties = true; + } + atLeastOneProperty = true; + } else { + atLeastOneProperty = addPropertyDef(required, properties, propertyName, bmmClass, bmmProperty); + } + } + + // Some properties are ignored in the parent, but must be added here, because redefinition is hard to do + // for openAPI code generators. So add there here + Set propertiesToAddFromParent = addAttributesFromParent.get(bmmClassName); + if(propertiesToAddFromParent != null) { + Map> flatProperties = bmmClass.getFlatProperties(); + for(String propertyName : propertiesToAddFromParent) { + BmmProperty bmmProperty = flatProperties.get(propertyName); + atLeastOneProperty = addPropertyDef(required, properties, propertyName, bmmClass, bmmProperty); + } + } + + //properties.add(typeNameProperty, jsonFactory.createObjectBuilder().add("type", "string"));//.add("pattern", "^" + typeName + "(<.*>)?$")); + JsonObjectBuilder definition = jsonFactory.createObjectBuilder() + .add("type", "object"); + if(hasRequiredProperties) { + definition.add("required", required); + } + + + if(!allowAdditionalProperties && atLeastOneProperty) { + definition.add("additionalProperties", false); + } + boolean addedAncestors = false; + JsonArrayBuilder polymorphicArray = jsonFactory.createArrayBuilder(); + if(ancestorOverrides.containsKey(originalTypeName)) { + List ancestors = ancestorOverrides.get(originalTypeName); + for (String ancestor : ancestors) { + //no ignore support for ancestor overrides, just do it right here + polymorphicArray.add(createReference(ancestor)); + addedAncestors = true; + } + } else if (ignoredAncestors.contains(bmmClassName)) { + addedAncestors = false; + } else { + for (BmmDefinedType ancestor : bmmClass.getAncestors().values()) { + if (!shouldAncestorBeIgnored(ancestor)) { + polymorphicArray.add(createReference(ancestor.getBaseClass().getName())); + addedAncestors = true; + } + } + } + if(addedAncestors) { + definition.add("properties", properties); + polymorphicArray.add(definition); + definitions.add(typeName, jsonFactory.createObjectBuilder().add("allOf", polymorphicArray).build()); + } else { + if(!bmmClass.getImmediateDescendants().isEmpty()) { + JsonObjectBuilder descendantsMappings = jsonFactory.createObjectBuilder(); + + Map allDescendants = bmmModel.getAllDescendantClassObjects(bmmClass); + for(BmmClass descendant:allDescendants.values()) { + if(shouldClassBeIncluded(descendant)) { + descendantsMappings.add(BmmDefinitions.typeNameToClassKey(descendant.getName()), createReferenceTarget(descendant.getName())); + } + } + properties.add(typeNameProperty, createType("string")); + definition.add("discriminator", jsonFactory.createObjectBuilder() + .add("propertyName", typeNameProperty) + .add("mapping",descendantsMappings) + ); + } + definition.add("properties", properties); + definitions.add(typeName, definition); + } + } + + private boolean addPropertyDef(JsonArrayBuilder required, JsonObjectBuilder properties, String propertyName, BmmClass bmmClass, BmmProperty bmmProperty) { + boolean atLeastOneProperty; + JsonObjectBuilder propertyDef = createPropertyDef(bmmClass, bmmProperty, bmmProperty.getType()); + extendPropertyDef(propertyDef, bmmProperty); + properties.add(propertyName, propertyDef); + + if (bmmProperty.getMandatory()) { + required.add(propertyName); + } + atLeastOneProperty = true; + return atLeastOneProperty; + } + + private boolean shouldAncestorBeIgnored(BmmDefinedType bmmClass) { + return //bmmClass.getTypeName().equalsIgnoreCase(baseType) || + isJSPrimitive(bmmClass) || this.ignoredTypes.contains(bmmClass.getTypeName().toUpperCase()); + } + + + private void extendPropertyDef(JsonObjectBuilder propertyDef, BmmProperty bmmProperty) { + if(bmmProperty instanceof BmmContainerProperty) { + BmmContainerProperty containerProperty = (BmmContainerProperty) bmmProperty; + if(containerProperty.getCardinality() != null && containerProperty.getCardinality().getLower() > 0) { + propertyDef.add("minItems", containerProperty.getCardinality().getLower()); + } + } + } + + private JsonObjectBuilder createPropertyDef(BmmClass clazz, BmmProperty property, BmmType type) { + String possiblyOverriddenType = getTypeOverride(clazz.getName(), property.getName()); + if(possiblyOverriddenType != null) { + return createType(possiblyOverriddenType); + } + if (type instanceof BmmParameterType) { + return createType("object"); + //nothing more to be done + } else if (type instanceof BmmSimpleType) { + BmmSimpleType simpleType = (BmmSimpleType) type; + if (isJSPrimitive(type)) { + return getJSPrimitive(simpleType); + } else { + return createPolymorphicReference(simpleType.getBaseClass()); + } + } else if (type instanceof BmmContainerType) { + BmmContainerType containerType = (BmmContainerType) type; + if(containerType.getBaseType().getTypeName().equalsIgnoreCase("Octet")) { + //binary data will be base64 encoded, so express that here + JsonObjectBuilder string = createType("string"); + string.add("format", "byte"); + return string; + } + return jsonFactory.createObjectBuilder() + .add("type", "array") + .add("items", createPropertyDef(clazz, property, containerType.getBaseType())); + } else if (type instanceof BmmGenericType) { + BmmGenericType genericType = (BmmGenericType) type; + if (isJSPrimitive(genericType)) { + return getJSPrimitive(genericType); + } else if (genericType.getBaseClass().getName().equalsIgnoreCase("Hash")) { + JsonObjectBuilder reference = createPolymorphicReference(genericType.getBaseClass()); + if ( genericType.getGenericParameters() != null && genericType.getGenericParameters().size() > 1 ) { + BmmType valueType = genericType.getGenericParameters().get(1); + //TODO: is this correct? + reference.add("additionalProperties", + createPropertyDef(clazz, property, valueType)); + //createPolymorphicReference(bmmModel.getClassDefinition(valueType.getTypeName()))); + } else { + reference.add("additionalProperties", true); + } + return reference; + + + } else { + return createPolymorphicReference(genericType.getBaseClass()); + } + + } + throw new IllegalArgumentException("type must be a BmmType, but was " + type.getClass().getSimpleName()); + + } + + private JsonObjectBuilder createPolymorphicReference(BmmClass type) { + + List descendants = getAllNonAbstractDescendants( type); + if(!type.isAbstract()) { + descendants.add(BmmDefinitions.typeNameToClassKey(type.getName())); + } + + if(descendants.isEmpty()) { + //this is an object of which only an abstract class exists. + //it cannot be represented as standard json, one would think. this is mainly access control and authored + //resource in the RM + return createType("object"); + } else if (descendants.size() > 1) { + + if(writeOneOf) { + JsonObjectBuilder polymorphicReference = jsonFactory.createObjectBuilder(); + JsonObjectBuilder descendantsMappings = jsonFactory.createObjectBuilder(); + + JsonArrayBuilder oneOfArray = jsonFactory.createArrayBuilder(); + for (String descendant : descendants) { + if(shouldClassBeIncluded(descendant)) { + JsonObjectBuilder typeReference = createReference(descendant); + oneOfArray.add(typeReference); + descendantsMappings.add(descendant, createReferenceTarget(descendant)); + } + } + + polymorphicReference.add("discriminator", + jsonFactory.createObjectBuilder() + .add("propertyName", typeNameProperty) + .add("mapping", descendantsMappings)); + polymorphicReference.add("oneOf", oneOfArray); + + return polymorphicReference; + } else { + return createReference(BmmDefinitions.typeNameToClassKey(type.getName())); + } + } else { + return createReference(descendants.get(0)); + } + + } + + + private List getAllNonAbstractDescendants(BmmClass bmmClass) { + List result = new ArrayList<>(); + List descs = bmmClass.getImmediateDescendants(); + for(String desc:descs) { + if(!bmmClass.getName().equalsIgnoreCase(desc)) {//TODO: fix getImmediateDescendants in BMM so this check is not required + BmmClass classDefinition = bmmModel.getClassDefinition(desc); + if (!classDefinition.isAbstract()) { + result.add(BmmDefinitions.typeNameToClassKey(classDefinition.getName())); + } + result.addAll(getAllNonAbstractDescendants(classDefinition)); + } + } + return result; + } + + private boolean isJSPrimitive(BmmType bmmType) { + return primitiveTypeMapping.containsKey(bmmType.getTypeName().toLowerCase()); + } + + private boolean isJSPrimitive(BmmClass bmmClass) { + return primitiveTypeMapping.containsKey(bmmClass.getName().toLowerCase()); + } + + + private JsonObjectBuilder getJSPrimitive(BmmType bmmType) { + return primitiveTypeMapping.get(bmmType.getTypeName().toLowerCase()).get(); + } + + private JsonObjectBuilder createRequiredArray(String... requiredFields) { + JsonArrayBuilder requiredArray = jsonFactory.createArrayBuilder(); + for(String requiredProperty: requiredFields) { + requiredArray.add(requiredProperty); + } + return jsonFactory.createObjectBuilder().add("required", requiredArray); + } + + + private JsonObjectBuilder createType(String jsPrimitive) { + return jsonFactory.createObjectBuilder().add("type", jsPrimitive); + } + + private JsonObjectBuilder createReference(String rootType) { + if(isJSPrimitive(rootType)) { + return this.primitiveTypeMapping.get(rootType.toLowerCase()).get(); + } +// if(rootType.equalsIgnoreCase("Any")) { +// return jsonFactory.createObjectBuilder().add("type", "object"); +// } + return jsonFactory.createObjectBuilder().add("$ref", createReferenceTarget(rootType)); + } + + private String createReferenceTarget(String typeName) { + return "#/components/schemas/" + convertTypeName(typeName); + } + + private boolean isJSPrimitive(String rootType) { + return this.primitiveTypeMapping.containsKey(rootType.toLowerCase()); + } + + public OpenAPIModelCreator allowAdditionalProperties(boolean allowAdditionalProperties) { + this.allowAdditionalProperties = allowAdditionalProperties; + return this; + } + + public OpenAPIModelCreator withTypePropertyName(String typeNameProperty) { + this.typeNameProperty = typeNameProperty; + return this; + } + + public OpenAPIModelCreator writeOneOf(boolean writeOneOf) { + this.writeOneOf = writeOneOf; + return this; + } + + public OpenAPIModelCreator withExampleRootTypeName(String exampleRootTypeName) { + this.exampleRootTypeName = exampleRootTypeName; + return this; + } + + public void addIgnoredType(String ignoredType) { + this.ignoredTypes.add(ignoredType); + } + + public void removeIgnoredType(String ignoredType) { + this.ignoredTypes.remove(ignoredType); + } + + /** + * Converts OpenEHR type naming to UpperCamelCase, as it should be for OpenAPI specs + * @param typeName + * @return + */ + public String convertTypeName(String typeName) { + switch(typeName) { + case "DV_EHR_URI": + return "DvEHRURI"; + case "UID_BASED_ID": + return "UIDBasedId"; + default: + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, typeName); + } + } + + public void ignoreAncestors(String multiplicity_interval) { + this.ignoredAncestors.add(multiplicity_interval); + } + + public void overrideType(String className, String propertyName, String overridenType) { + typeOverrides.put(new AttributeReference(className, propertyName), overridenType); + } + + private String getTypeOverride(String className, String propertyName) { + return typeOverrides.get(new AttributeReference(className, propertyName)); + } +} diff --git a/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperties.java b/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperties.java new file mode 100644 index 000000000..ba84b6357 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperties.java @@ -0,0 +1,122 @@ +package com.nedap.archie.openapi; + +import org.openehr.bmm.core.BmmClass; +import org.openehr.bmm.core.BmmModel; +import org.openehr.bmm.core.BmmProperty; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Detects properties that are redefined in a specialized class - so they exist in the superclass in BMM + * and in the subclass, but have a different type in the subclass + * + * Used because this is not supported by openAPI generators - they should only exist in the subclass! + */ +public class RedefinedProperties { + + Map propertiesByParentClass = new HashMap<>(); + Map propertiesByChildClass = new HashMap<>(); + + public RedefinedProperties(BmmModel model) { + storeProperties(detectProperties(model)); + + detectExtraPropertiesToAdd(model); + } + + private List detectProperties(BmmModel model) { + final List properties = new ArrayList<>(); + model.getClassDefinitions().forEach( (name, bmmClass) -> { + properties.addAll(detect(model, bmmClass)); + } ); + return properties; + } + + private void storeProperties(List properties) { + properties.forEach( property -> { + propertiesByParentClass.put( + createPropertyName(property.getSuperClass().getName(), property.getSuperClassProperty().getName()), + property); + propertiesByChildClass.put( + createPropertyName(property.getSubClass().getName(), property.getSubClassProperty().getName()), + property); + }); + } + + private List detect(BmmModel model, BmmClass bmmClass) { + //get all the properties defined directly in this class, so not in the parent + List propertiesDefinedInParent = new ArrayList<>(); + Map> properties = bmmClass.getProperties(); + List ancestors = getAllAncestors(model, bmmClass); + + + for(BmmProperty property:properties.values()) { + ancestors.forEach( (ancestor) -> { + if(ancestor.getProperties().get(property.getName()) != null) { + RedefinedProperty redefinedProperty = new RedefinedProperty(bmmClass, property, ancestor, ancestor.getProperties().get(property.getName())); + propertiesDefinedInParent.add(redefinedProperty); + } + }); + } + + return propertiesDefinedInParent; + } + + private List getAllAncestors(BmmModel model, BmmClass bmmClass) { + return bmmClass.findAllAncestors().stream() + .map(a -> model.getClassDefinition(a) + ).collect(Collectors.toList()); + } + + /** + * Finds all the properties in all classes that are redefined in one of the subclasses, + * but not in all subclasses. Since they are removed from the superclass in openapi, + * they must be added to the other subclasses as well. + * @param model + */ + private void detectExtraPropertiesToAdd(BmmModel model) { + propertiesByParentClass.forEach( (name, property) -> { + List descendants = property.getSuperClass().findAllDescendants() + .stream() + .map(a -> model.getClassDefinition(a) + ).collect(Collectors.toList()); + descendants.forEach( descendant -> { + if(!descendant.isAbstract() && + !descendant.getProperties().containsKey(property.getSuperClassProperty().getName()) && + !descendant.findAllAncestors().contains(property.getSubClass().getName()) && + !property.getSuperClassProperty().getName().equals("uid")) { + //ok, found one! + //technically not correct - this can add things twice in the hierarchy, leading to the same problem again + //might be good enough. + System.out.println(descendant.getName() + "." + property.getSuperClassProperty().getName()); + } + }); + }); + model.getClassDefinitions().forEach( (name, bmmClass) -> { + List ancestors = getAllAncestors(model, bmmClass); + ancestors.forEach( ancestor -> { + ancestor.getProperties().values().forEach( ancestorProperty -> { + RedefinedProperty propertyInParent = getPropertyByParent(ancestor.getName(), ancestorProperty.getName()); + if(propertyInParent != null) { + //we found a property in the parent. + } + }); + }); + }); + } + + public RedefinedProperty getPropertyByParent(String parentName, String propertyName) { + return propertiesByParentClass.get(createPropertyName(parentName, propertyName)); + } + + public RedefinedProperty getPropertyByChild(String parentName, String propertyName) { + return propertiesByChildClass.get(createPropertyName(parentName, propertyName)); + } + + private String createPropertyName(String parentName, String propertyName) { + return parentName + "." + propertyName; + } +} diff --git a/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperty.java b/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperty.java new file mode 100644 index 000000000..a7dcb5fac --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/openapi/RedefinedProperty.java @@ -0,0 +1,41 @@ +package com.nedap.archie.openapi; + +import org.openehr.bmm.core.BmmClass; +import org.openehr.bmm.core.BmmProperty; + +public class RedefinedProperty { + + private BmmClass subClass; + private BmmProperty subClassProperty; + private BmmClass superClass; + private BmmProperty superClassProperty; + + public RedefinedProperty(BmmClass subClass, BmmProperty subClassProperty, BmmClass superClass, BmmProperty superClassProperty) { + this.subClass = subClass; + this.subClassProperty = subClassProperty; + this.superClass = superClass; + this.superClassProperty = superClassProperty; + } + + public BmmClass getSubClass() { + return subClass; + } + + public BmmProperty getSubClassProperty() { + return subClassProperty; + } + + public BmmClass getSuperClass() { + return superClass; + } + + public BmmProperty getSuperClassProperty() { + return superClassProperty; + } + + public String toString() { + return superClass.getName() + "." + superClassProperty.getName() + + " redefined in " + + subClass.getName() + "." + subClassProperty.getName(); + } +} diff --git a/tools/src/test/java/com/nedap/archie/json/JSONSchemaCreatorTest.java b/tools/src/test/java/com/nedap/archie/json/JSONSchemaCreatorTest.java index a756fa7d9..581f073e2 100644 --- a/tools/src/test/java/com/nedap/archie/json/JSONSchemaCreatorTest.java +++ b/tools/src/test/java/com/nedap/archie/json/JSONSchemaCreatorTest.java @@ -17,26 +17,47 @@ public class JSONSchemaCreatorTest { @Test public void createSchema() { BmmModel model = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.1.0").getModel(); - JsonObject jsonObject = new JSONSchemaCreator().create(model); + Map schemas = new JSONSchemaCreator().create(model); Map config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config); + printSchemas(schemas, jsonWriterFactory); - jsonWriterFactory.createWriter(System.out).write(jsonObject); + } + + private void printSchemas(Map schemas, JsonWriterFactory jsonWriterFactory) { + for(JsonSchemaUri name:schemas.keySet()) { + JsonObject schema = schemas.get(name); + System.out.println("printing schema " + name + ":"); + jsonWriterFactory.createWriter(System.out).write(schema); + } } @Test public void createSchemaWithoutAdditionalProperties() { BmmModel model = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.1.0").getModel(); - JsonObject jsonObject = new JSONSchemaCreator().allowAdditionalProperties(false).create(model); + Map schemas = new JSONSchemaCreator().allowAdditionalProperties(false).create(model); + Map config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config); + printSchemas(schemas, jsonWriterFactory); + } + + @Test + public void createMultiFileSchemaWithoutAdditionalProperties() { + BmmModel model = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.1.0").getModel(); + Map schemas = new JSONSchemaCreator().allowAdditionalProperties(false).splitInMultipleFiles(true).create(model); + + + Map config = new HashMap<>(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config); - jsonWriterFactory.createWriter(System.out).write(jsonObject); + printSchemas(schemas, jsonWriterFactory); } } diff --git a/tools/src/test/java/com/nedap/archie/openapi/ModelInfoLookupToBmmConverterTest.java b/tools/src/test/java/com/nedap/archie/openapi/ModelInfoLookupToBmmConverterTest.java new file mode 100644 index 000000000..28c21c3c2 --- /dev/null +++ b/tools/src/test/java/com/nedap/archie/openapi/ModelInfoLookupToBmmConverterTest.java @@ -0,0 +1,120 @@ +package com.nedap.archie.openapi; + +import com.google.common.collect.Sets; +import com.nedap.archie.json.flat.AttributeReference; +import com.nedap.archie.rminfo.ArchieAOMInfoLookup; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonWriterFactory; +import jakarta.json.stream.JsonGenerator; +import org.junit.Test; +import org.openehr.bmm.v2.persistence.PBmmSchema; +import org.openehr.bmm.v2.persistence.odin.BmmOdinParser; +import org.openehr.bmm.v2.persistence.odin.BmmOdinSerializer; +import org.openehr.bmm.v2.validation.BmmRepository; +import org.openehr.bmm.v2.validation.BmmSchemaConverter; +import org.openehr.bmm.v2.validation.BmmValidationResult; +import org.w3c.dom.Attr; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +public class ModelInfoLookupToBmmConverterTest { + + @Test + public void testAom() throws Exception { + Set ignoredAttributes = Sets.newHashSet(); +// new AttributeReference("C_OBJECT", "archetype"), +// new AttributeReference("C_COMPLEX_OBJECT", "archetype"), +// new AttributeReference("C_ATTRIBUTE", "archetype"), +// new AttributeReference("ARCHETYPE_TERMINOLOGY", "owner_archetype"), +// new AttributeReference("TEMPLATE_OVERLAY", "owning_template"), +// new AttributeReference("ARCHETYPE_CONSTRAINT", "parent"), +// new AttributeReference("ARCHETYPE_CONSTRAINT", "soc_parent") +// ); + + + + + BmmRepository bmmRepository = new BmmRepository(); + try(InputStream stream = getClass().getResourceAsStream("openehr_base_for_aom.bmm")) { + PBmmSchema baseShema = BmmOdinParser.convert(stream); + bmmRepository.addPersistentSchema(baseShema); + } + BmmSchemaConverter bmmSchemaConverter = new BmmSchemaConverter(bmmRepository); + bmmSchemaConverter.validateAndConvertRepository(); + BmmValidationResult baseModel = bmmRepository.getModels().get(0); + assertTrue(baseModel.getLogger().toString(), baseModel.passes()); + ModelInfoLookupToPBmmConverter modelInfoLookupToPBmmConverter = new ModelInfoLookupToPBmmConverter(); + + //this makes sure we get the JSON version, not something else + modelInfoLookupToPBmmConverter.setExcludedJsonIgnoreFields(true); + + PBmmSchema schema = modelInfoLookupToPBmmConverter.convert(ArchieAOMInfoLookup.getInstance(), ignoredAttributes, baseModel.getModel()); + System.out.println(new BmmOdinSerializer().serialize(schema)); + BmmValidationResult bmmValidationResult = bmmSchemaConverter.validateConvertAndAddToRepo(schema); + assertTrue(bmmValidationResult.getLogger().toString(), bmmValidationResult.passes()); + + } + + + @Test + public void testAomOpenAPI() throws Exception { + Set ignoredAttributes = Sets.newHashSet(); + ignoredAttributes.add(new AttributeReference("C_PRIMITIVE_OBJECT", "node_id")); + + BmmRepository bmmRepository = new BmmRepository(); + try(InputStream stream = getClass().getResourceAsStream("openehr_base_for_aom.bmm")) { + PBmmSchema baseSchema = BmmOdinParser.convert(stream); + bmmRepository.addPersistentSchema(baseSchema); + } + BmmSchemaConverter bmmSchemaConverter = new BmmSchemaConverter(bmmRepository); + bmmSchemaConverter.validateAndConvertRepository(); + BmmValidationResult baseModel = bmmRepository.getModels().get(0); + assertTrue(baseModel.getLogger().toString(), baseModel.passes()); + + ModelInfoLookupToPBmmConverter modelInfoLookupToPBmmConverter = new ModelInfoLookupToPBmmConverter(); + //this makes sure we get the JSON version, not something else + modelInfoLookupToPBmmConverter.setExcludedJsonIgnoreFields(true); + PBmmSchema schema = modelInfoLookupToPBmmConverter.convert(ArchieAOMInfoLookup.getInstance(), ignoredAttributes, baseModel.getModel()); + + BmmValidationResult bmmValidationResult = bmmSchemaConverter.validateConvertAndAddToRepo(schema); + assertTrue(bmmValidationResult.getLogger().toString(), bmmValidationResult.passes()); + + OpenAPIModelCreator creator = new OpenAPIModelCreator() + .allowAdditionalProperties(true) + .withTypePropertyName("@type") + .writeOneOf(true) + .withExampleRootTypeName("Archetype"); + + //not part of the AOM, but get added because of reasons, remove them. + creator.addIgnoredType("OBJECT_REF"); + creator.addIgnoredType("LOCATABLE_REF"); + creator.addIgnoredType("PARTY_REF"); + creator.addIgnoredType("ACCESS_GROUP_REF"); + //this class should not be added, it is an internal Archie construct and not used in json + creator.addIgnoredType("LANGUAGE_SECTION"); + //TODO: these should not be present by default, but should be added to the RM generator! + creator.removeIgnoredType("TRANSLATION_DETAILS"); + creator.removeIgnoredType("AUTHORED_RESOURCE"); + creator.removeIgnoredType("RESOURCE_DESCRIPTION"); + creator.removeIgnoredType("RESOURCE_DESCRIPTION_ITEM"); + creator.ignoreAncestors("MULTIPLICITY_INTERVAL"); + //this is not picked up correctly by generics (and it shouldn't be), so needs manual mapping + creator.overrideType("MULTIPLICITY_INTERVAL", "lower", "integer"); + creator.overrideType("MULTIPLICITY_INTERVAL", "upper", "integer"); + + JsonObject jsonObject = creator.create(bmmValidationResult.getModel()); + Map config = new HashMap(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config); + + + jsonWriterFactory.createWriter(System.out).write(jsonObject); + + } +} diff --git a/tools/src/test/java/com/nedap/archie/openapi/OpenAPIModelCreatorTest.java b/tools/src/test/java/com/nedap/archie/openapi/OpenAPIModelCreatorTest.java new file mode 100644 index 000000000..3d0776859 --- /dev/null +++ b/tools/src/test/java/com/nedap/archie/openapi/OpenAPIModelCreatorTest.java @@ -0,0 +1,63 @@ +package com.nedap.archie.openapi; + +import com.nedap.archie.json.flat.AttributeReference; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonWriterFactory; +import jakarta.json.stream.JsonGenerator; +import org.junit.Test; +import org.openehr.bmm.core.BmmModel; +import org.openehr.referencemodels.BuiltinReferenceModels; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class OpenAPIModelCreatorTest { + + @Test + public void createSchema() { + BmmModel model = BuiltinReferenceModels.getBmmRepository().getModel("openehr_rm_1.0.4").getModel(); + OpenAPIModelCreator openAPIModelCreator = new OpenAPIModelCreator().allowAdditionalProperties(true); + + //let's do some annoying polymorphism mapping! + Set ignoredAttributes = new HashSet<>(); + //abstract any item. Not useful at all. + ignoredAttributes.add(new AttributeReference("EXTRACT_CONTENT_ITEM", "item")); + //generics redefinition. No generics support, so just DV_INTERVAL here. + ignoredAttributes.add(new AttributeReference("DV_QUANTITY", "normal_range")); + ignoredAttributes.add(new AttributeReference("DV_PROPORTION", "normal_range")); + ignoredAttributes.add(new AttributeReference("DV_COUNT", "normal_range")); + //same generics redefinition + ignoredAttributes.add(new AttributeReference("DV_QUANTITY", "other_reference_ranges")); + ignoredAttributes.add(new AttributeReference("DV_PROPORTION", "other_reference_ranges")); + ignoredAttributes.add(new AttributeReference("DV_COUNT", "other_reference_ranges")); + //uid is always the same except in extract. Let's keep it the same everywhere. + ignoredAttributes.add(new AttributeReference("EXTRACT_REQUEST", "uid")); + ignoredAttributes.add(new AttributeReference("PARTY", "uid")); + ignoredAttributes.add(new AttributeReference("EXTRACT_ACTION_REQUEST", "uid")); + //this makes id more specific, but objectref is also concrete, so let's not do that. + ignoredAttributes.add(new AttributeReference("LOCATABLE_REF", "id")); + + //accuracy: redefined. Ignore, define in concrete types instead + //keep DV_AMOUNT and DV_TEMPORAL here + ignoredAttributes.add(new AttributeReference("DV_QUANTIFIED", "accuracy")); + ignoredAttributes.add(new AttributeReference("DV_ABSOLUTE_QUANTITY", "accuracy")); + + List attributesFromParent = new ArrayList<>(); + openAPIModelCreator.setIgnoredAttributes(ignoredAttributes); + + openAPIModelCreator.setAddAttributesFromParent(attributesFromParent); + JsonObject jsonObject = openAPIModelCreator.create(model); + + Map config = new HashMap(); + config.put(JsonGenerator.PRETTY_PRINTING, true); + JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config); + + + jsonWriterFactory.createWriter(System.out).write(jsonObject); + } +} diff --git a/tools/src/test/resources/com/nedap/archie/openapi/openehr_base_for_aom.bmm b/tools/src/test/resources/com/nedap/archie/openapi/openehr_base_for_aom.bmm new file mode 100644 index 000000000..1dcb54494 --- /dev/null +++ b/tools/src/test/resources/com/nedap/archie/openapi/openehr_base_for_aom.bmm @@ -0,0 +1,583 @@ +-- +-- component: openEHR Implementation Technology Specification (ITS) +-- description: openEHR BASE Component schema. This file format is based on the BMM specification +-- http://www.openehr.org/releases/BASE/latest/docs/bmm/bmm.html +-- keywords: reference model, meta-model, archetypes +-- author: Thomas Beale +-- support: https://openehr.atlassian.net/projects/SPECPR +-- copyright: Copyright (c) 2016 openEHR Foundation +-- license: Apache 2.0 +-- + +------------------------------------------------------ +-- BMM version on which these schemas are based. +------------------------------------------------------ +bmm_version = <"2.3"> + +------------------------------------------------------ +-- schema identification +-- (schema_id computed as __) +------------------------------------------------------ +rm_publisher = <"openehr"> +schema_name = <"base"> +rm_release = <"1.1.0"> + +------------------------------------------------------ +-- schema documentation +------------------------------------------------------ +schema_revision = <"1.1.0.1"> +schema_lifecycle_state = <"stable"> +schema_description = <"openEHR Release 1.1.0 BASE model foundation and base types packages"> +schema_author = <"Thomas Beale "> + +------------------------------------------------------ +-- packages +------------------------------------------------------ +packages = < + ["org.openehr.base.foundation_types"] = < + name = <"org.openehr.base.foundation_types"> + packages = < + ["base_types"] = < + name = <"base_types"> + classes = <"Any", "Ordered", "Numeric", "Ordered_Numeric", "Octet", "Byte", "Boolean", "Integer", + "Integer64", "Real", "Double", "Character", "String", "Uri", "Terminology_code", "Terminology_term" + > + > + ["structures"] = < + name = <"structures"> + classes = <"List", "Array", "Set", "Hash", "Container"> + > + ["interval"] = < + name = <"interval"> + classes = <"Interval"> + > + ["iso8601_time"] = < + name = <"iso8601_time"> + classes = < + "Temporal", "Iso8601_type", + "Date", "Time", "Date_time", "Duration", + "Iso8601_date", "Iso8601_time", "Iso8601_date_time", "Iso8601_duration" + > + > + > + > + ["org.openehr.base.base_types"] = < + name = <"org.openehr.base.base_types"> + packages = < + ["definitions"] = < + name = <"definitions"> + classes = <"VALIDITY_KIND", "VERSION_STATUS"> + > + ["identification"] = < + name = <"identification"> + classes = <"OBJECT_REF", "OBJECT_ID", "UID", "LOCATABLE_REF", "PARTY_REF", "ACCESS_GROUP_REF", + "TERMINOLOGY_ID", + "UID_BASED_ID", "GENERIC_ID", "ARCHETYPE_ID", "ARCHETYPE_HRID", "TEMPLATE_ID", "OBJECT_VERSION_ID", + "HIER_OBJECT_ID", "VERSION_TREE_ID", "INTERNET_ID", "UUID", "ISO_OID" + > + > + > + > +> + +------------------------------------------------------ +-- primitive types +------------------------------------------------------ + +primitive_types = < + ["Any"] = < + name = <"Any"> + is_abstract = + > + ["Ordered"] = < + name = <"Ordered"> + is_abstract = + documentation = <"Ancestor of types with total order relation defined, i.e. '<' and '='."> + ancestors = <"Any"> + > + ["Numeric"] = < + name = <"Numeric"> + is_abstract = + documentation = <"Ancestor of numeric types."> + ancestors = <"Any"> + > + ["Ordered_Numeric"] = < + name = <"Ordered_Numeric"> + is_abstract = + documentation = <"Ancestor of ordered numeric types."> + ancestors = <"Numeric", "Ordered"> + > + ["Byte"] = < + name = <"Byte"> + ancestors = <"Ordered"> + > + ["Octet"] = < + name = <"Octet"> + ancestors = <"Ordered"> + > + ["Boolean"] = < + name = <"Boolean"> + ancestors = <"Any"> + > + ["Integer"] = < + name = <"Integer"> + ancestors = <"Ordered_Numeric"> + > + ["Integer64"] = < + name = <"Integer64"> + ancestors = <"Ordered_Numeric"> + > + ["Real"] = < + name = <"Real"> + ancestors = <"Ordered_Numeric"> + > + ["Double"] = < + name = <"Double"> + ancestors = <"Ordered_Numeric"> + > + ["Character"] = < + name = <"Character"> + ancestors = <"Ordered"> + > + ["String"] = < + name = <"String"> + ancestors = <"Ordered"> + > + ["Uri"] = < + name = <"Uri"> + ancestors = <"String"> + > + + ["Temporal"] = < + name = <"Temporal"> + ancestors = <"Ordered"> + is_abstract = + > + + ["Iso8601_type"] = < + name = <"Iso8601_type"> + documentation = <"Parent of ISO8601 types."> + ancestors = <"Temporal"> + properties = < + ["value"] = (P_BMM_SINGLE_PROPERTY) < + name = <"value"> + type = <"String"> + > + > + > + + ["Date"] = < + name = <"Date"> + documentation = <"Date type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Time"] = < + name = <"Time"> + documentation = <"Time type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Date_time"] = < + name = <"Date_time"> + documentation = <"Date Time type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Duration"] = < + name = <"Duration"> + documentation = <"Duration type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Iso8601_date"] = < + name = <"Iso8601_date"> + documentation = <"Date type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Iso8601_time"] = < + name = <"Iso8601_time"> + documentation = <"Time type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Iso8601_date_time"] = < + name = <"Iso8601_date_time"> + documentation = <"Date Time type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Iso8601_duration"] = < + name = <"Iso8601_duration"> + documentation = <"Duration type based on IS8601 representation."> + ancestors = <"Iso8601_type"> + > + + ["Terminology_term"] = < + name = <"Terminology_term"> + ancestors = <"Any"> + properties = < + ["text"] = (P_BMM_SINGLE_PROPERTY) < + name = <"text"> + type = <"String"> + is_mandatory = + > + ["concept"] = (P_BMM_SINGLE_PROPERTY) < + name = <"concept"> + type = <"Terminology_code"> + is_mandatory = + > + > + > + + ["Terminology_code"] = < + name = <"Terminology_code"> + ancestors = <"Any"> + properties = < + ["terminology_id"] = (P_BMM_SINGLE_PROPERTY) < + name = <"terminology_id"> + type = <"String"> + is_mandatory = + > + ["terminology_version"] = (P_BMM_SINGLE_PROPERTY) < + name = <"terminology_version"> + type = <"String"> + > + ["code_string"] = (P_BMM_SINGLE_PROPERTY) < + name = <"code_string"> + type = <"String"> + is_mandatory = + > + ["uri"] = (P_BMM_SINGLE_PROPERTY) < + name = <"uri"> + type = <"Uri"> + is_mandatory = + > + > + > + + ["Container"] = < + name = <"Container"> + ancestors = <"Any"> + generic_parameter_defs = < + ["V"] = < + name = <"V"> + > + > + is_abstract = + > + + ["List"] = < + name = <"List"> + ancestors = <"Container"> + generic_parameter_defs = < + ["V"] = < + name = <"V"> + > + > + > + + ["Array"] = < + name = <"Array"> + ancestors = <"Container"> + generic_parameter_defs = < + ["V"] = < + name = <"V"> + > + > + > + + ["Set"] = < + name = <"Set"> + ancestors = <"Container"> + generic_parameter_defs = < + ["V"] = < + name = <"V"> + > + > + > + + ["Interval"] = < + name = <"Interval"> + documentation = <"Type defining an interval of any ordered type."> + ancestors = <"Any"> + generic_parameter_defs = < + ["T"] = < + name = <"T"> + conforms_to_type = <"Ordered"> + > + > + properties = < + ["lower"] = (P_BMM_SINGLE_PROPERTY_OPEN) < + name = <"lower"> + type = <"T"> + > + ["upper"] = (P_BMM_SINGLE_PROPERTY_OPEN) < + name = <"upper"> + type = <"T"> + > + ["lower_unbounded"] = (P_BMM_SINGLE_PROPERTY) < + name = <"lower_unbounded"> + type = <"Boolean"> + is_mandatory = + > + ["upper_unbounded"] = (P_BMM_SINGLE_PROPERTY) < + name = <"upper_unbounded"> + type = <"Boolean"> + is_mandatory = + > + ["lower_included"] = (P_BMM_SINGLE_PROPERTY) < + name = <"lower_included"> + type = <"Boolean"> + is_mandatory = + > + ["upper_included"] = (P_BMM_SINGLE_PROPERTY) < + name = <"upper_included"> + type = <"Boolean"> + is_mandatory = + > + > + > + + ["Hash"] = < + name = <"Hash"> + documentation = <"Type defining Hash table / hash map structure, whose type parameters V and K represent a value type and an Ordered key type respectively."> + ancestors = <"Container"> + generic_parameter_defs = < + ["K"] = < + name = <"K"> + conforms_to_type = <"Ordered"> + > + ["V"] = < + name = <"V"> + > + > + > + +> + +------------------------------------------------------ +-- classes +------------------------------------------------------ + +class_definitions = < + + ------------------------------------------------------------ + ------------------ base.base_types.identification --------------- + ------------------------------------------------------------ + + ["OBJECT_REF"] = < + name = <"OBJECT_REF"> + ancestors = <"Any"> + properties = < + ["id"] = (P_BMM_SINGLE_PROPERTY) < + name = <"id"> + type = <"OBJECT_ID"> + is_mandatory = + > + ["namespace"] = (P_BMM_SINGLE_PROPERTY) < + name = <"namespace"> + type = <"String"> + is_mandatory = + > + ["type"] = (P_BMM_SINGLE_PROPERTY) < + name = <"type"> + type = <"String"> + is_mandatory = + > + > + > + + ["LOCATABLE_REF"] = < + name = <"LOCATABLE_REF"> + ancestors = <"OBJECT_REF"> + properties = < + ["id"] = (P_BMM_SINGLE_PROPERTY) < + name = <"id"> + type = <"UID_BASED_ID"> + is_mandatory = + > + ["path"] = (P_BMM_SINGLE_PROPERTY) < + name = <"path"> + type = <"String"> + is_im_infrastructure = + > + > + > + + ["PARTY_REF"] = < + name = <"PARTY_REF"> + ancestors = <"OBJECT_REF"> + > + + ["ACCESS_GROUP_REF"] = < + name = <"ACCESS_GROUP_REF"> + ancestors = <"OBJECT_REF"> + > + + ["OBJECT_ID"] = < + name = <"OBJECT_ID"> + is_abstract = + ancestors = <"Any"> + properties = < + ["value"] = (P_BMM_SINGLE_PROPERTY) < + name = <"value"> + type = <"String"> + is_mandatory = + > + > + > + + ["TERMINOLOGY_ID"] = < + name = <"TERMINOLOGY_ID"> + ancestors = <"OBJECT_ID"> + > + + ["UID_BASED_ID"] = < + name = <"UID_BASED_ID"> + ancestors = <"OBJECT_ID"> + is_abstract = + > + + ["GENERIC_ID"] = < + name = <"GENERIC_ID"> + ancestors = <"OBJECT_ID"> + properties = < + ["scheme"] = (P_BMM_SINGLE_PROPERTY) < + name = <"scheme"> + type = <"String"> + is_mandatory = + > + > + > + + ["ARCHETYPE_ID"] = < + name = <"ARCHETYPE_ID"> + ancestors = <"OBJECT_ID"> + > + + ["ARCHETYPE_HRID"] = < + name = <"ARCHETYPE_HRID"> + ancestors = <"Any"> + properties = < + ["namespace"] = (P_BMM_SINGLE_PROPERTY) < + name = <"namespace"> + type = <"String"> + is_mandatory = + > + ["rm_publisher"] = (P_BMM_SINGLE_PROPERTY) < + name = <"rm_publisher"> + type = <"String"> + is_mandatory = + > + ["rm_package"] = (P_BMM_SINGLE_PROPERTY) < + name = <"rm_package"> + type = <"String"> + is_mandatory = + > + ["rm_class"] = (P_BMM_SINGLE_PROPERTY) < + name = <"rm_class"> + type = <"String"> + is_mandatory = + > + ["concept_id"] = (P_BMM_SINGLE_PROPERTY) < + name = <"concept_id"> + type = <"String"> + is_mandatory = + > + ["release_version"] = (P_BMM_SINGLE_PROPERTY) < + name = <"release_version"> + type = <"String"> + is_mandatory = + > + ["version_status"] = (P_BMM_SINGLE_PROPERTY) < + name = <"version_status"> + type = <"VERSION_STATUS"> + is_mandatory = + > + ["build_count"] = (P_BMM_SINGLE_PROPERTY) < + name = <"build_count"> + type = <"String"> + is_mandatory = + > + > + > + + ["TEMPLATE_ID"] = < + name = <"TEMPLATE_ID"> + ancestors = <"OBJECT_ID"> + > + + ["OBJECT_VERSION_ID"] = < + name = <"OBJECT_VERSION_ID"> + ancestors = <"UID_BASED_ID"> + > + + ["HIER_OBJECT_ID"] = < + name = <"HIER_OBJECT_ID"> + ancestors = <"UID_BASED_ID"> + > + + ["VERSION_TREE_ID"] = < + name = <"VERSION_TREE_ID"> + ancestors = <"Any"> + properties = < + ["value"] = (P_BMM_SINGLE_PROPERTY) < + name = <"value"> + type = <"String"> + is_mandatory = + > + > + > + + ["UID"] = < + name = <"UID"> + is_abstract = + ancestors = <"Any"> + properties = < + ["value"] = (P_BMM_SINGLE_PROPERTY) < + name = <"value"> + type = <"String"> + is_mandatory = + > + > + > + + ["INTERNET_ID"] = < + name = <"INTERNET_ID"> + ancestors = <"UID"> + > + + ["UUID"] = < + name = <"UUID"> + ancestors = <"UID"> + > + + ["ISO_OID"] = < + name = <"ISO_OID"> + ancestors = <"UID"> + > + + -- + --------------------- base.base_types.definitions ------------------ + -- + + ["VALIDITY_KIND"] = (P_BMM_ENUMERATION_STRING) < + name = <"VALIDITY_KIND"> + ancestors = <"String"> + item_names = <"mandatory", "optional", "prohibited"> + > + + ["VERSION_STATUS"] = (P_BMM_ENUMERATION_STRING) < + name = <"VERSION_STATUS"> + ancestors = <"String"> + item_names = <"alpha", "beta", "release_candidate", "released", "build"> + > + + -- + --------------------- base.rsource ------------------ + -- + + +>