/*
* 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
*
* http://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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC §105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.utility.export;
//~--- JDK imports ------------------------------------------------------------
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.time.Year;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import gov.va.med.term.vhat.xml.model.ActionType;
import gov.va.med.term.vhat.xml.model.DesignationType;
import gov.va.med.term.vhat.xml.model.KindType;
import gov.va.med.term.vhat.xml.model.PropertyType;
import gov.va.med.term.vhat.xml.model.Terminology;
import gov.va.med.term.vhat.xml.model.Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation
.SubsetMemberships.SubsetMembership;
import gov.va.med.term.vhat.xml.model.Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships
.Relationship;
import sh.isaac.MetaData;
import sh.isaac.api.Get;
import sh.isaac.api.LookupService;
import sh.isaac.api.State;
import sh.isaac.api.TaxonomyService;
import sh.isaac.api.chronicle.LatestVersion;
import sh.isaac.api.chronicle.ObjectChronology;
import sh.isaac.api.collections.ConceptSequenceSet;
import sh.isaac.api.component.concept.ConceptChronology;
import sh.isaac.api.component.concept.ConceptVersion;
import sh.isaac.api.component.sememe.SememeChronology;
import sh.isaac.api.component.sememe.SememeType;
import sh.isaac.api.component.sememe.version.DescriptionSememe;
import sh.isaac.api.component.sememe.version.DynamicSememe;
import sh.isaac.api.component.sememe.version.SememeVersion;
import sh.isaac.api.component.sememe.version.StringSememe;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeColumnInfo;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeData;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUtility;
import sh.isaac.api.constants.DynamicSememeConstants;
import sh.isaac.api.coordinate.StampCoordinate;
import sh.isaac.api.coordinate.StampPrecedence;
import sh.isaac.api.identity.StampedVersion;
import sh.isaac.mapping.constants.IsaacMappingConstants;
import sh.isaac.MetaData;
import sh.isaac.model.configuration.LanguageCoordinates;
import sh.isaac.model.coordinate.StampCoordinateImpl;
import sh.isaac.model.coordinate.StampPositionImpl;
import sh.isaac.provider.query.associations.AssociationInstance;
import sh.isaac.provider.query.associations.AssociationUtilities;
import sh.isaac.utility.Frills;
//~--- classes ----------------------------------------------------------------
/**
* The Class VetsExporter.
*/
public class VetsExporter {
/** The log. */
private final Logger log = LogManager.getLogger();
/** The designation types. */
private final Map<UUID, String> designationTypes = new HashMap<>();
/** The property types. */
private final Map<UUID, String> propertyTypes = new HashMap<>();
/** The relationship types. */
private final Map<UUID, String> relationshipTypes = new HashMap<>();
/** The assemblages map. */
private final Map<UUID, String> assemblagesMap = new HashMap<>();
/** The subset map. */
private final Map<String, Long> subsetMap = new HashMap<>();
/** The stamp coordinates. */
private StampCoordinate STAMP_COORDINATES = null;
/** The ts. */
TaxonomyService ts = Get.taxonomyService();
// TODO: Source all the following hardcoded UUID values from MetaData, once available
// ConceptChronology: VHAT Attribute Types <261> uuid:8287530a-b6b0-594d-bf46-252e09434f7e
/** The vhat property types UUID. */
// VHAT Metadata -> "Attribute Types"
final UUID vhatPropertyTypesUUID = UUID.fromString("8287530a-b6b0-594d-bf46-252e09434f7e");
/** The vhat property types nid. */
final int vhatPropertyTypesNid = Get.identifierService()
.getNidForUuids(this.vhatPropertyTypesUUID);
// ConceptChronology: Refsets (ISAAC) <325> uuid:fab80263-6dae-523c-b604-c69e450d8c7f
/** The vhat refset types UUID. */
// VHAT Metadata -> "Refsets"
final UUID vhatRefsetTypesUUID = UUID.fromString("fab80263-6dae-523c-b604-c69e450d8c7f");
/** The vhat refset types nid. */
final int vhatRefsetTypesNid = Get.identifierService()
.getNidForUuids(this.vhatRefsetTypesUUID);
/** The code assemblage UUID. */
// conceptChronology: CODE (ISAAC) <77> uuid:803af596-aea8-5184-b8e1-45f801585d17
final UUID codeAssemblageUUID = MetaData.CODE.getPrimordialUuid();
/** The code assemblage concept seq. */
final int codeAssemblageConceptSeq = Get.identifierService()
.getConceptSequenceForUuids(this.codeAssemblageUUID);
// ConceptChronology: VHAT <1129> uuid:6e60d7fd-3729-5dd3-9ce7-6d97c8f75447
/** The vhat code system UUID. */
// VHAT CodeSystem
final UUID vhatCodeSystemUUID = UUID.fromString("6e60d7fd-3729-5dd3-9ce7-6d97c8f75447");
/** The vhat code system nid. */
final int vhatCodeSystemNid = Get.identifierService()
.getNidForUuids(this.vhatCodeSystemUUID);
// ConceptChronology: Preferred Name (ISAAC) <257> uuid:a20e5175-6257-516a-a97d-d7f9655916b8
/** The preferred name extended type. */
// VHAT Description Types -> Preferred Name
final UUID preferredNameExtendedType = UUID.fromString("a20e5175-6257-516a-a97d-d7f9655916b8");
// ConceptChronology: Association Types (ISAAC) <309> uuid:55f56c52-757a-5db8-bf1e-3ed613711386
/** The vhat association types UUID. */
// ISAAC Associations => RelationshipType UUID
final UUID vhatAssociationTypesUUID = UUID.fromString("55f56c52-757a-5db8-bf1e-3ed613711386");
// ConceptChronology: Description Types (ISAAC) <254> uuid:09c43aa9-eaed-5217-bc5f-23cacca4df38
/** The vhat designation types UUID. */
// ISAAC Descriptions => DesignationType UUID
final UUID vhatDesignationTypesUUID = UUID.fromString("09c43aa9-eaed-5217-bc5f-23cacca4df38");
/** The vhat all concepts UUID. */
// ConceptChronology: All VHAT Concepts (ISAAC) <365> uuid:f2df3cf5-a426-50f9-a660-081a5ca22c70
final UUID vhatAllConceptsUUID = UUID.fromString("f2df3cf5-a426-50f9-a660-081a5ca22c70");
/** The missing SDO code systems UUID. */
// ConceptChronology: Missing SDO Code System Concepts <42268> uuid:52460eeb-1388-512d-a5e4-fddd64fe0aee
final UUID missingSDOCodeSystemsUUID = UUID.fromString("52460eeb-1388-512d-a5e4-fddd64fe0aee");
/** The full export mode. */
boolean fullExportMode = false;
/** The terminology. */
private Terminology terminology;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new vets exporter.
*/
public VetsExporter() {}
//~--- methods -------------------------------------------------------------
/**
* Export.
*
* @param writeTo the output stream object handling the export
* @param startDate only export concepts modified on or after this date. Set to 0, if you want to start from the beginning
* @param endDate only export concepts where their most recent modified date is on or before this date. Set to Long.MAX_VALUE to get everything.
* @param fullExportMode if true, exports all content present at the end of the date range, ignoring start date. All actions are set to add.
* If false - delta mode - calculates the action based on the start and end dates, and includes only the minimum required elements in the xml file.
*/
public void export(OutputStream writeTo, long startDate, long endDate, boolean fullExportMode) {
this.fullExportMode = fullExportMode;
this.STAMP_COORDINATES = new StampCoordinateImpl(StampPrecedence.PATH,
new StampPositionImpl(endDate, MetaData.DEVELOPMENT_PATH.getConceptSequence()),
ConceptSequenceSet.EMPTY,
State.ANY_STATE_SET);
// Build Assemblages map
Get.sememeService().getAssemblageTypes().forEach((assemblageSeqId) -> {
this.assemblagesMap.put(Get.conceptSpecification(assemblageSeqId)
.getPrimordialUuid(),
Get.conceptSpecification(assemblageSeqId)
.getConceptDescriptionText());
});
// XML object
this.terminology = new Terminology();
// Types
this.terminology.setTypes(new Terminology.Types());
Terminology.Types.Type xmlType;
// Subsets/Refsets
this.terminology.setSubsets(new Terminology.Subsets());
// CodeSystem
final Terminology.CodeSystem xmlCodeSystem = new Terminology.CodeSystem();
final Terminology.CodeSystem.Version xmlVersion = new Terminology.CodeSystem.Version();
final Terminology.CodeSystem.Version.CodedConcepts xmlCodedConcepts =
new Terminology.CodeSystem.Version.CodedConcepts();
// Add to map
Get.taxonomyService().getAllRelationshipOriginSequences(Get.identifierService()
.getNidForUuids(this.vhatAssociationTypesUUID)).forEach((conceptId) -> {
final ConceptChronology<? extends ConceptVersion<?>> concept = Get.conceptService()
.getConcept(conceptId);
this.relationshipTypes.put(concept.getPrimordialUuid(),
getPreferredNameDescriptionType(concept.getNid()));
});
if (fullExportMode) {
// Build XML
for (final String s: this.relationshipTypes.values()) {
xmlType = new Terminology.Types.Type();
xmlType.setKind(KindType.RELATIONSHIP_TYPE);
xmlType.setName(s);
this.terminology.getTypes()
.getType()
.add(xmlType);
}
}
// Add to map
Get.taxonomyService().getAllRelationshipOriginSequences(Get.identifierService()
.getNidForUuids(this.vhatPropertyTypesUUID)).forEach((conceptId) -> {
final ConceptChronology<? extends ConceptVersion<?>> concept = Get.conceptService()
.getConcept(conceptId);
this.propertyTypes.put(concept.getPrimordialUuid(),
getPreferredNameDescriptionType(concept.getNid()));
});
if (fullExportMode) {
// Build XML
for (final String s: this.propertyTypes.values()) {
xmlType = new Terminology.Types.Type();
xmlType.setKind(KindType.PROPERTY_TYPE);
xmlType.setName(s);
this.terminology.getTypes()
.getType()
.add(xmlType);
}
}
// Add to map
Get.taxonomyService().getAllRelationshipOriginSequences(Get.identifierService()
.getNidForUuids(this.vhatDesignationTypesUUID)).forEach((conceptId) -> {
final ConceptChronology<? extends ConceptVersion<?>> concept = Get.conceptService()
.getConcept(conceptId);
this.designationTypes.put(concept.getPrimordialUuid(),
getPreferredNameDescriptionType(concept.getNid()));
});
if (fullExportMode) {
// Build XML
for (final String s: this.designationTypes.values()) {
xmlType = new Terminology.Types.Type();
xmlType.setKind(KindType.DESIGNATION_TYPE);
xmlType.setName(s);
this.terminology.getTypes()
.getType()
.add(xmlType);
}
}
// Get data, Add to map
Get.taxonomyService().getAllRelationshipOriginSequences(Get.identifierService()
.getNidForUuids(this.vhatRefsetTypesUUID)).forEach((tcs) -> {
final ConceptChronology<? extends ConceptVersion<?>> concept = Get.conceptService()
.getConcept(tcs);
// Excluding these:
if (concept.getPrimordialUuid().equals(this.vhatAllConceptsUUID) ||
concept.getPrimordialUuid().equals(this.missingSDOCodeSystemsUUID) ||
Frills.definesMapping(concept.getConceptSequence())) {
// Skip
} else {
final Terminology.Subsets.Subset xmlSubset = new Terminology.Subsets.Subset();
xmlSubset.setAction(determineAction(concept, startDate, endDate));
xmlSubset.setName(getPreferredNameDescriptionType(concept.getNid()));
xmlSubset.setActive(concept.isLatestVersionActive(this.STAMP_COORDINATES));
// read VUID
xmlSubset.setVUID(Frills.getVuId(concept.getNid(), this.STAMP_COORDINATES)
.orElse(null));
if (xmlSubset.getVUID() == null) {
this.log.warn("Failed to find VUID for subset concept " + concept.getPrimordialUuid());
}
if (xmlSubset.getAction() != ActionType.NONE) {
this.terminology.getSubsets()
.getSubset()
.add(xmlSubset);
}
this.subsetMap.put(xmlSubset.getName(), xmlSubset.getVUID());
}
});
final ConceptChronology<? extends ConceptVersion<?>> vhatConcept = Get.conceptService()
.getConcept(this.vhatCodeSystemNid);
xmlCodeSystem.setAction(ActionType.NONE);
xmlCodeSystem.setName(getPreferredNameDescriptionType(vhatConcept.getNid()));
xmlCodeSystem.setVUID(Frills.getVuId(this.vhatCodeSystemNid, null)
.orElse(null));
xmlCodeSystem.setDescription(
"VHA Terminology"); // This is in an acceptable synonym, but easier to hard code at the moment...
xmlCodeSystem.setCopyright(Year.now()
.getValue() + "");
xmlCodeSystem.setCopyrightURL("");
xmlCodeSystem.setPreferredDesignationType("Preferred Name");
xmlVersion.setAppend(Boolean.TRUE);
xmlVersion.setName("Authoring Version");
xmlVersion.setDescription("Delta output from ISAAC");
try {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
final String formattedDate = sdf.format(System.currentTimeMillis());
final XMLGregorianCalendar xmlEffDate = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(formattedDate);
final XMLGregorianCalendar xmlRelDate = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(formattedDate);
xmlVersion.setEffectiveDate(xmlEffDate);
xmlVersion.setReleaseDate(xmlRelDate);
} catch (final Exception pe) {
this.log.error("Misconfiguration of date parser!", pe);
}
xmlVersion.setSource("");
final AtomicInteger skippedForNonVHAT = new AtomicInteger();
final AtomicInteger skippedDateRange = new AtomicInteger();
final AtomicInteger observedVhatConcepts = new AtomicInteger();
final AtomicInteger exportedVhatConcepts = new AtomicInteger();
final List<Terminology.CodeSystem.Version.MapSets.MapSet> xmlMapSetCollection = new ArrayList<>();
Get.conceptService().getConceptChronologyStream().forEach((concept) -> {
if (fullExportMode) {
Get.sememeService()
.getSememesForComponentFromAssemblage(concept.getConceptSequence(),
IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_SEMEME_TYPE
.getConceptSequence())
.forEach(mappingSememe -> {
final Terminology.CodeSystem.Version.MapSets.MapSet xmlMapSet =
new Terminology.CodeSystem.Version.MapSets.MapSet();
xmlMapSet.setAction(determineAction(concept, startDate, endDate));
xmlMapSet.setActive(concept.isLatestVersionActive(this.STAMP_COORDINATES));
xmlMapSet.setCode(getCodeFromNid(concept.getNid()));
xmlMapSet.setName(getPreferredNameDescriptionType(concept.getNid()));
xmlMapSet.setVUID(Frills.getVuId(concept.getNid(), this.STAMP_COORDINATES)
.orElse(null));
// Source and Target CodeSystem
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends DynamicSememe>> mappingSememeVersion =
((SememeChronology) mappingSememe).getLatestVersion(DynamicSememe.class,
this.STAMP_COORDINATES);
if (mappingSememeVersion.isPresent()) {
// Get referenced component for the MapSet values
final ConceptChronology<? extends ConceptVersion<?>> cc = Get.conceptService()
.getConcept(
mappingSememeVersion.get()
.value()
.getReferencedComponentNid());
@SuppressWarnings({ "rawtypes", "unchecked" })
final Optional<LatestVersion<ConceptVersion<?>>> cv =
((ConceptChronology) cc).getLatestVersion(ConceptVersion.class,
this.STAMP_COORDINATES);
if (cv.isPresent()) {
Get.sememeService()
.getSememesForComponentFromAssemblage(cv.get()
.value()
.getChronology()
.getNid(),
IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_STRING_EXTENSION
.getConceptSequence())
.forEach(mappingStrExt -> {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends DynamicSememe>> mappingStrExtVersion =
((SememeChronology) mappingStrExt).getLatestVersion(
DynamicSememe.class, this.STAMP_COORDINATES);
// TODO:DA review
if (mappingStrExtVersion.isPresent()) {
final DynamicSememeData dsd[] = mappingStrExtVersion.get()
.value()
.getData();
if (dsd.length == 2) {
if (dsd[0].getDataObject()
.equals(IsaacMappingConstants.get().MAPPING_SOURCE_CODE_SYSTEM
.getNid())) {
xmlMapSet.setSourceCodeSystem(dsd[1].getDataObject()
.toString());
} else if (dsd[0].getDataObject()
.equals(
IsaacMappingConstants.get().MAPPING_SOURCE_CODE_SYSTEM_VERSION
.getNid())) {
xmlMapSet.setSourceVersionName(dsd[1].getDataObject()
.toString());
} else if (dsd[0].getDataObject()
.equals(IsaacMappingConstants.get().MAPPING_TARGET_CODE_SYSTEM
.getNid())) {
xmlMapSet.setTargetCodeSystem(dsd[1].getDataObject()
.toString());
} else if (dsd[0].getDataObject()
.equals(
IsaacMappingConstants.get().MAPPING_TARGET_CODE_SYSTEM_VERSION
.getNid())) {
xmlMapSet.setTargetVersionName(dsd[1].getDataObject()
.toString());
}
}
}
});
// MapEntries
// MapEntry->Properties
// TODO: MapEntry->Designations (currently ignored - none found in import XML, importer doesn't implement)
// TODO: MapEntry->Relationships (currently ignored - none found in import XML, importer doesn't implement)
// TODO:DA review - not using MapEntryType as it doesn't allow for .setProperties(), needed for GEM_Flags
final Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries xmlMapEntries =
new Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries();
for (
final Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry me:
readMapEntryTypes(cv.get()
.value()
.getChronology()
.getNid(),
startDate,
endDate)) {
xmlMapEntries.getMapEntry()
.add(me);
}
xmlMapSet.setMapEntries(xmlMapEntries);
}
}
// Designations
final Terminology.CodeSystem.Version.MapSets.MapSet.Designations xmlMapSetDesignations =
new Terminology.CodeSystem.Version.MapSets.MapSet.Designations();
for (final DesignationType d: getDesignations(concept,
startDate,
endDate,
(() -> new Terminology.CodeSystem.Version.MapSets.MapSet.Designations.Designation()))) {
// MapSets contain a phantom description with no typeName, code or VUID - need to keep those out
// There's probably a more appropriate way to do this - quick and dirty for now
// TODO:DA review
if (!(((d.getTypeName() == null) || d.getTypeName().isEmpty()) &&
((d.getCode() == null) || d.getCode().isEmpty()) &&
(d.getVUID() == null))) {
xmlMapSetDesignations.getDesignation()
.add(
(Terminology.CodeSystem.Version.MapSets.MapSet.Designations.Designation) d);
}
}
xmlMapSet.setDesignations(xmlMapSetDesignations);
// Properties
final Terminology.CodeSystem.Version.MapSets.MapSet.Properties xmlMapSetProperties =
new Terminology.CodeSystem.Version.MapSets.MapSet.Properties();
for (final PropertyType pt: readPropertyTypes(concept.getNid(),
startDate,
endDate,
() -> new Terminology.CodeSystem.Version.MapSets.MapSet.Properties.Property())) {
xmlMapSetProperties.getProperty()
.add(
(Terminology.CodeSystem.Version.MapSets.MapSet.Properties.Property) pt);
}
xmlMapSet.setProperties(xmlMapSetProperties);
if ((xmlMapSet.getAction() != ActionType.NONE) ||
((xmlMapSet.getMapEntries() != null) &&
(xmlMapSet.getMapEntries().getMapEntry().size() > 0))) {
xmlMapSetCollection.add(xmlMapSet);
}
});
}
if (!this.ts.wasEverKindOf(concept.getConceptSequence(), this.vhatCodeSystemNid)) {
// Needed to ignore all the dynamically created/non-imported concepts
skippedForNonVHAT.getAndIncrement();
} else if (concept.getNid() == this.vhatCodeSystemNid) {
// skip
} else {
observedVhatConcepts.getAndIncrement();
final int conceptNid = concept.getNid();
if (!wasConceptOrNestedValueModifiedInDateRange(concept, startDate)) {
skippedDateRange.getAndIncrement();
} else {
exportedVhatConcepts.getAndIncrement();
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept xmlCodedConcept =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept();
xmlCodedConcept.setAction(determineAction(concept, startDate, endDate));
xmlCodedConcept.setName(getPreferredNameDescriptionType(conceptNid));
xmlCodedConcept.setVUID(Frills.getVuId(conceptNid, null)
.orElse(null));
xmlCodedConcept.setCode(getCodeFromNid(conceptNid));
xmlCodedConcept.setActive(
Boolean.valueOf(concept.isLatestVersionActive(this.STAMP_COORDINATES)));
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations xmlDesignations =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations();
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Properties xmlProperties =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Properties();
for (final DesignationType d: getDesignations(concept,
startDate,
endDate,
() -> new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation())) {
xmlDesignations.getDesignation()
.add(
(Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation) d);
}
for (final PropertyType pt: readPropertyTypes(concept.getNid(),
startDate,
endDate,
() -> new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Properties.Property())) {
xmlProperties.getProperty()
.add(
(Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Properties.Property) pt);
}
// Relationships
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships xmlRelationships =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships();
for (final Relationship rel: getRelationships(concept, startDate, endDate)) {
xmlRelationships.getRelationship()
.add(rel);
}
// Try to keep XML output somewhat clean, without empty elements (i.e. <Element/> or <Element></Element>
if (xmlDesignations.getDesignation()
.size() > 0) {
xmlCodedConcept.setDesignations(xmlDesignations);
}
if (xmlProperties.getProperty()
.size() > 0) {
xmlCodedConcept.setProperties(xmlProperties);
}
if (xmlRelationships.getRelationship()
.size() > 0) {
xmlCodedConcept.setRelationships(xmlRelationships);
}
// Add all CodedConcept elements
xmlCodedConcepts.getCodedConcept()
.add(xmlCodedConcept);
}
}
});
// Close out XML
xmlVersion.setCodedConcepts(xmlCodedConcepts);
// MapSets
if (xmlMapSetCollection.size() > 0) {
final Terminology.CodeSystem.Version.MapSets xmlMapSets = new Terminology.CodeSystem.Version.MapSets();
xmlMapSets.getMapSet()
.addAll(xmlMapSetCollection);
xmlVersion.setMapSets(xmlMapSets);
}
xmlCodeSystem.setVersion(xmlVersion);
this.terminology.setCodeSystem(xmlCodeSystem);
this.log.info("Skipped " + skippedForNonVHAT.get() + " concepts for non-vhat");
this.log.info("Skipped " + skippedDateRange.get() + " concepts for outside date range");
this.log.info("Processed " + observedVhatConcepts.get() + " concepts");
this.log.info("Exported " + exportedVhatConcepts.get() + " concepts");
writeXml(writeTo);
}
/**
* Builds the property.
*
* @param sememe the sememe
* @param startDate the start date
* @param endDate the end date
* @param constructor the constructor
* @return A PropertyType object for the property, or null
*/
private PropertyType buildProperty(SememeChronology<?> sememe,
long startDate,
long endDate,
Supplier<PropertyType> constructor) {
String newValue = null;
String oldValue = null;
boolean isActive = false;
if (sememe.getSememeType() == SememeType.DYNAMIC) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends DynamicSememe>> sememeVersion =
((SememeChronology) sememe).getLatestVersion(DynamicSememe.class,
this.STAMP_COORDINATES);
if (sememeVersion.isPresent() &&
(sememeVersion.get().value().getData() != null) &&
(sememeVersion.get().value().getData().length > 0)) {
newValue = (sememeVersion.get()
.value()
.getData()[0] == null) ? null
: sememeVersion.get()
.value()
.getData()[0]
.dataToString();
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<DynamicSememe<?>> coll =
((SememeChronology) sememe).getVisibleOrderedVersionList(this.STAMP_COORDINATES);
Collections.reverse(coll);
for (final DynamicSememe<?> s: coll) {
if (s.getTime() < startDate) {
oldValue = (s.getData()[0] != null) ? s.getData()[0]
.dataToString()
: null;
break;
}
}
isActive = sememeVersion.get()
.value()
.getState() == State.ACTIVE;
}
} else if (sememe.getSememeType() == SememeType.STRING) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends StringSememe>> sememeVersion =
((SememeChronology) sememe).getLatestVersion(StringSememe.class,
this.STAMP_COORDINATES);
if (sememeVersion.isPresent()) {
newValue = sememeVersion.get()
.value()
.getString();
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<StringSememe<?>> coll =
((SememeChronology) sememe).getVisibleOrderedVersionList(this.STAMP_COORDINATES);
Collections.reverse(coll);
for (final StringSememe<?> s: coll) {
if (s.getTime() < startDate) {
oldValue = s.getString();
break;
}
}
isActive = sememeVersion.get()
.value()
.getState() == State.ACTIVE;
}
} else {
this.log.warn("Unexpectedly passed sememe " + sememe + " when we only expected a dynamic or a string type");
return null;
}
if (newValue == null) {
return null;
}
final PropertyType property = (constructor == null) ? new gov.va.med.term.vhat.xml.model.PropertyType()
: constructor.get();
property.setAction(determineAction(sememe, startDate, endDate));
if (isActive && (property.getAction() == ActionType.NONE) && newValue.equals(oldValue)) {
return null;
} else if (!newValue.equals(oldValue) && (property.getAction() == ActionType.NONE)) {
// change action to update if the new and old values are not the same.
property.setAction(ActionType.UPDATE);
}
// got to here, there is change.
property.setActive(isActive);
if (((property.getAction() == ActionType.UPDATE) || (property.getAction() == ActionType.ADD)) &&
!newValue.equals(oldValue)) {
property.setValueNew(newValue);
}
if ((oldValue != null) && (property.getAction() != ActionType.ADD)) {
property.setValueOld(oldValue);
}
if (property.getAction() == ActionType.NONE) {
return null;
}
property.setTypeName(getPreferredNameDescriptionType(Get.identifierService()
.getConceptNid(sememe.getAssemblageSequence())));
return property;
}
/**
* Builds the subset membership.
*
* @param sememe the Chronicle object (concept) representing the Subset
* @param startDate the start date
* @param endDate the end date
* @return the SubsetMembership object built for the sememe, or null
*/
private SubsetMembership buildSubsetMembership(SememeChronology<?> sememe, long startDate, long endDate) {
if (sememe.getSememeType() == SememeType.DYNAMIC) {
final SubsetMembership subsetMembership = new SubsetMembership();
subsetMembership.setActive(sememe.isLatestVersionActive(this.STAMP_COORDINATES));
subsetMembership.setAction(determineAction(sememe, startDate, endDate));
if (subsetMembership.getAction() == ActionType.NONE) {
return null;
}
final long vuid = Frills.getVuId(Get.identifierService()
.getConceptNid(sememe.getAssemblageSequence()),
this.STAMP_COORDINATES)
.orElse(0L)
.longValue();
if (vuid > 0) {
subsetMembership.setVUID(vuid);
} else {
this.log.warn("No VUID found for Subset UUID: " + sememe.getPrimordialUuid());
}
return subsetMembership;
} else {
this.log.error("Unexpected sememe type! " + sememe);
return null;
}
}
/**
* Determine action.
*
* @param object the object
* @param startDate the start date
* @param endDate the end date
* @return the ActionType object representing the change
*/
private ActionType determineAction(ObjectChronology<? extends StampedVersion> object, long startDate, long endDate) {
ActionType action = ActionType.ADD;
if (!this.fullExportMode) {
final List<? extends StampedVersion> versions = object.getVersionList();
versions.sort((o1, o2) -> -1 * Long.compare(o1.getTime(), o2.getTime()));
boolean latest = true;
int versionCountInDateRange = 0;
int actionCountPriorToStartDate = 0;
State beginState = null;
State endState = null;
for (final StampedVersion sv: versions) {
if (sv.getTime() < startDate) {
// last value prior to start date
if (beginState == null) {
beginState = sv.getState();
}
actionCountPriorToStartDate++;
}
if ((sv.getTime() <= endDate) && (sv.getTime() >= startDate)) {
// last value prior to end date
if (endState == null) {
endState = sv.getState();
}
versionCountInDateRange++;
}
latest = false;
}
if ((beginState == null) && (endState != null)) {
action = ActionType.ADD;
} else if ((beginState != null) && (endState == null)) {
action = ActionType.NONE;
} else {
if (beginState != endState) {
action = ActionType.UPDATE;
} else {
if (versionCountInDateRange == 0) {
action = ActionType.NONE;
} else if (versionCountInDateRange > 0) {
action = ActionType.UPDATE;
} else if ((actionCountPriorToStartDate > 0) && (versionCountInDateRange > 0)) {
return ActionType.UPDATE;
}
/*
* The UI does not allow Remove. Only Active and Inactive. May be revisited in R3
* else if (finalStateIsInactive && actionCountPriorToStartDate > 0)
* {
* return ActionType.REMOVE;
* }
*/
}
}
}
return action;
}
/**
* Read map entry types.
*
* @param componentNid the component nid
* @param startDate the start date
* @param endDate the end date
* @return a List of the MapEntry objects for the MapSet item
*/
private List<Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry> readMapEntryTypes(int componentNid,
long startDate,
long endDate) {
final ArrayList<Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry> mes = new ArrayList<>();
Get.sememeService().getSememesFromAssemblage(Get.identifierService()
.getConceptSequence(componentNid)).forEach(sememe -> {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends DynamicSememe>> sememeVersion =
((SememeChronology) sememe).getLatestVersion(DynamicSememe.class, this.STAMP_COORDINATES);
if (sememeVersion.isPresent() && (sememeVersion.get().value().getData() !=
null) && (sememeVersion.get().value().getData().length > 0)) {
try {
final Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry me =
new Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry();
me.setAction(
ActionType.ADD); // TODO: There is currently no requirement or ability to deploy MapSet deltas, this if for the full export only
me.setVUID(Frills.getVuId(sememe.getNid(), this.STAMP_COORDINATES)
.orElse(null));
String code = getCodeFromNid(sememeVersion.get()
.value()
.getReferencedComponentNid());
if (null == code) {
code = Frills.getDescription(sememeVersion.get()
.value()
.getReferencedComponentNid())
.orElse("");
}
me.setSourceCode(code);
final boolean isActive = sememeVersion.get()
.value()
.getState() == State.ACTIVE;
me.setActive(isActive);
final DynamicSememeUtility ls = LookupService.get()
.getService(DynamicSememeUtility.class);
if (ls == null) {
throw new RuntimeException(
"An implementation of DynamicSememeUtility is not available on the classpath");
} else {
final DynamicSememeColumnInfo[] dsci =
ls.readDynamicSememeUsageDescription(sememeVersion.get()
.value()
.getAssemblageSequence())
.getColumnInfo();
final DynamicSememeData dsd[] = sememeVersion.get()
.value()
.getData();
for (final DynamicSememeColumnInfo d: dsci) {
final UUID columnUUID = d.getColumnDescriptionConcept();
final int col = d.getColumnOrder();
if ((null != dsd[col]) && (null != columnUUID)) {
if (columnUUID.equals(
DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMN_ASSOCIATION_TARGET_COMPONENT
.getPrimordialUuid())) {
me.setTargetCode(Frills.getDescription(UUID.fromString(dsd[col].getDataObject()
.toString()))
.orElse(""));
} else if (
columnUUID.equals(
IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_EQUIVALENCE_TYPE
.getPrimordialUuid())) {
// Currently ignored, no XML representation
} else if (
columnUUID.equals(
IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_SEQUENCE
.getPrimordialUuid())) {
me.setSequence(Integer.parseInt(dsd[col].getDataObject()
.toString()));
} else if (
columnUUID.equals(
IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_GROUPING
.getPrimordialUuid())) {
me.setGrouping(dsd[col].getDataObject());
} else if (
columnUUID.equals(
IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_EFFECTIVE_DATE
.getPrimordialUuid())) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
final String formattedDate = sdf.format(dsd[col].getDataObject());
me.setEffectiveDate(DatatypeFactory.newInstance()
.newXMLGregorianCalendar(formattedDate));
} else if (
columnUUID.equals(
IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_GEM_FLAGS
.getPrimordialUuid())) {
final Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry.Properties.Property gem_prop =
new Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry.Properties.Property();
final Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry.Properties props =
new Terminology.CodeSystem.Version.MapSets.MapSet.MapEntries.MapEntry.Properties();
gem_prop.setAction(ActionType.ADD); // See 'TODO' above
gem_prop.setActive(
Boolean.TRUE); // TODO: Defaulting to true as it appears in the import XML, not sure how to determine otherwise
gem_prop.setTypeName("GEM_Flags");
gem_prop.setValueNew(dsd[col].getDataObject()
.toString());
props.getProperty()
.add(gem_prop);
me.setProperties(props);
} else {
this.log.warn("No mapping match found for UUID: ", columnUUID);
}
}
}
mes.add(me);
}
} catch (final NumberFormatException nfe) {
this.log.error("Misconfiguration of integer parser!", nfe);
} catch (final IllegalArgumentException iae) {
this.log.error("Misconfiguration of date parser!", iae);
} catch (final NullPointerException npe) {
this.log.error("Misconfiguration of date parser!", npe);
} catch (final DatatypeConfigurationException dce) {
this.log.error("Misconfiguration of date parser!", dce);
} catch (final Exception e) {
this.log.error("General MapEntry failure!", e);
}
}
});
return mes;
}
/**
* Read property types.
*
* @param componentNid the component nid
* @param startDate the start date
* @param endDate the end date
* @param constructor the constructor
* @return a List of the PropertyType objects for the specific component
*/
private List<PropertyType> readPropertyTypes(int componentNid,
long startDate,
long endDate,
Supplier<PropertyType> constructor) {
final ArrayList<PropertyType> pts = new ArrayList<>();
Get.sememeService()
.getSememesForComponent(componentNid)
.forEach((sememe) -> {
// skip code and vuid properties - they have special handling
if ((sememe.getAssemblageSequence() != MetaData.VUID.getConceptSequence()) &&
(sememe.getAssemblageSequence() != this.codeAssemblageConceptSeq) &&
this.ts.wasEverKindOf(sememe.getAssemblageSequence(), this.vhatPropertyTypesNid)) {
final PropertyType property = buildProperty(sememe, startDate, endDate, constructor);
if (property != null) {
pts.add(property);
}
}
});
return pts;
}
/**
* Scan through all (nested) components associated with this concept, and the concept itself, and see if the latest edit
* date for any component is within our filter range.
*
* @param concept the concept
* @param startDate the start date
* @return true or false, if the concept or a nested value was modified within the date range
*/
@SuppressWarnings("rawtypes")
private boolean wasConceptOrNestedValueModifiedInDateRange(ConceptChronology concept, long startDate) {
@SuppressWarnings("unchecked")
final Optional<LatestVersion<ConceptVersion>> cv = concept.getLatestVersion(ConceptVersion.class,
this.STAMP_COORDINATES);
if (cv.isPresent()) {
if (cv.get()
.value()
.getTime() >= startDate) {
return true;
}
}
return hasSememeModifiedInDateRange(concept.getNid(), startDate);
}
/**
* Write xml.
*
* @param writeTo the write to
*/
private void writeXml(OutputStream writeTo) {
try {
final JAXBContext jaxbContext = JAXBContext.newInstance(Terminology.class);
final Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(this.terminology, writeTo);
} catch (final Exception e) {
this.log.error("Unexpected", e);
throw new RuntimeException(e);
}
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the code from nid.
*
* @param componentNid the component nid
* @return the Code value found based on the Nid
*/
private String getCodeFromNid(int componentNid) {
final Optional<SememeChronology<? extends SememeVersion<?>>> sc = Get.sememeService()
.getSememesForComponentFromAssemblage(
componentNid,
this.codeAssemblageConceptSeq)
.findFirst();
if (sc.isPresent()) {
// There was a bug in the older terminology loaders which loaded 'Code' as a static sememe, but marked it as a dynamic sememe.
// So during edits, new entries would get saves as dynamic sememes, while old entries were static. Handle either....
if (sc.get()
.getSememeType() == SememeType.STRING) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<StringSememe<?>>> sv =
((SememeChronology) sc.get()).getLatestVersion(StringSememe.class,
this.STAMP_COORDINATES);
if (sv.isPresent()) {
return sv.get()
.value()
.getString();
}
} else if (sc.get()
.getSememeType() == SememeType.DYNAMIC) // this path will become dead code, after the data is fixed.
{
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<? extends DynamicSememe>> sv =
((SememeChronology) sc.get()).getLatestVersion(DynamicSememe.class,
this.STAMP_COORDINATES);
if (sv.isPresent()) {
if ((sv.get().value().getData() != null) && (sv.get().value().getData().length == 1)) {
return sv.get()
.value()
.getData()[0]
.dataToString();
}
}
} else {
this.log.error("Unexpected sememe type for 'Code' sememe on nid " + componentNid);
}
}
return null;
}
/**
* Gets the designations.
*
* @param concept the concept
* @param startDate the start date
* @param endDate the end date
* @param constructor the constructor
* @return a List of DesignationTypes for the concept
*/
private List<DesignationType> getDesignations(ConceptChronology<?> concept,
long startDate,
long endDate,
Supplier<DesignationType> constructor) {
final List<DesignationType> designations = new ArrayList<>();
Get.sememeService()
.getSememesForComponent(concept.getNid())
.forEach(sememe -> {
if (sememe.getSememeType() == SememeType.DESCRIPTION) {
boolean hasChild = false;
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<DescriptionSememe>> descriptionVersion =
((SememeChronology) sememe).getLatestVersion(DescriptionSememe.class,
this.STAMP_COORDINATES);
if (descriptionVersion.isPresent()) {
final DesignationType d = constructor.get();
d.setAction(determineAction(sememe, startDate, endDate));
if (d.getAction() != ActionType.ADD) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<DescriptionSememe<?>> coll =
((SememeChronology) sememe).getVisibleOrderedVersionList(this.STAMP_COORDINATES);
Collections.reverse(coll);
for (final DescriptionSememe<?> s: coll) {
if (s.getTime() < startDate) {
d.setValueOld(s.getText());
break;
}
}
}
if ((d.getAction() == ActionType.UPDATE) || (d.getAction() == ActionType.ADD)) {
d.setValueNew(descriptionVersion.get()
.value()
.getText());
}
if ((d.getValueNew() != null) &&
(d.getValueOld() != null) &&
d.getValueNew().equals(d.getValueOld())) {
d.setValueOld(null);
d.setValueNew(null);
}
d.setCode(getCodeFromNid(sememe.getNid()));
d.setVUID(Frills.getVuId(sememe.getNid(), this.STAMP_COORDINATES)
.orElse(null));
d.setActive(descriptionVersion.get()
.value()
.getState() == State.ACTIVE);
// Get the extended type
final Optional<UUID> descType =
Frills.getDescriptionExtendedTypeConcept(this.STAMP_COORDINATES,
sememe.getNid());
if (descType.isPresent()) {
d.setTypeName(this.designationTypes.get(descType.get()));
} else {
this.log.warn("No extended description type present on description " +
sememe.getPrimordialUuid() + " " +
descriptionVersion.get().value().getText());
}
if (d instanceof
gov.va.med.term.vhat.xml.model.Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation) {
// Read any nested properties or subset memberships on this description
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation.Properties xmlDesignationProperties =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation.Properties();
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation.SubsetMemberships xmlSubsetMemberships =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation.SubsetMemberships();
Get.sememeService()
.getSememesForComponent(sememe.getNid())
.forEach((nestedSememe) -> {
// skip code and vuid properties - they are handled already
if ((nestedSememe.getAssemblageSequence() !=
MetaData.VUID.getConceptSequence()) &&
(nestedSememe.getAssemblageSequence() !=
this.codeAssemblageConceptSeq)) {
if (this.ts.wasEverKindOf(nestedSememe.getAssemblageSequence(),
this.vhatPropertyTypesNid)) {
final PropertyType property = buildProperty(nestedSememe,
startDate,
endDate,
null);
if (property != null) {
xmlDesignationProperties.getProperty()
.add(property);
}
}
// a refset that doesn't represent a mapset
else if (this.ts.wasEverKindOf(nestedSememe.getAssemblageSequence(),
this.vhatRefsetTypesNid) &&
!this.ts.wasEverKindOf(nestedSememe.getAssemblageSequence(),
IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_SEMEME_TYPE.getNid())) {
final SubsetMembership sm = buildSubsetMembership(nestedSememe,
startDate,
endDate);
if (sm != null) {
xmlSubsetMemberships.getSubsetMembership()
.add(sm);
}
}
}
});
if (xmlDesignationProperties.getProperty()
.size() > 0) {
((gov.va.med.term.vhat.xml.model.Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation) d).setProperties(
xmlDesignationProperties);
hasChild = true;
}
if (xmlSubsetMemberships.getSubsetMembership()
.size() > 0) {
((gov.va.med.term.vhat.xml.model.Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Designations.Designation) d).setSubsetMemberships(
xmlSubsetMemberships);
hasChild = true;
}
}
if ((d.getAction() != ActionType.NONE) || hasChild) {
designations.add(d);
}
}
}
});
return designations;
}
/**
* Gets the preferred name description type.
*
* @param conceptNid the concept nid
* @return the preferred description type for the concept
*/
private String getPreferredNameDescriptionType(int conceptNid) {
final ArrayList<String> descriptions = new ArrayList<>(1);
final ArrayList<String> inActiveDescriptions = new ArrayList<>(1);
Get.sememeService()
.getDescriptionsForComponent(conceptNid)
.forEach(sememeChronology -> {
@SuppressWarnings({ "rawtypes", "unchecked" })
final Optional<LatestVersion<DescriptionSememe<?>>> latestVersion =
((SememeChronology) sememeChronology).getLatestVersion(DescriptionSememe.class,
this.STAMP_COORDINATES);
if (latestVersion.isPresent() &&
this.preferredNameExtendedType.equals(
Frills.getDescriptionExtendedTypeConcept(this.STAMP_COORDINATES,
sememeChronology.getNid()).orElse(null))) {
if (latestVersion.get()
.value()
.getState() == State.ACTIVE) {
descriptions.add(latestVersion.get()
.value()
.getText());
} else {
inActiveDescriptions.add(latestVersion.get()
.value()
.getText());
}
}
});
if (descriptions.size() == 0) {
descriptions.addAll(inActiveDescriptions);
}
if (descriptions.size() == 0) {
// This doesn't happen for concept that represent subsets, for example.
this.log.debug("Failed to find a description flagged as preferred on concept " +
Get.identifierService().getUuidPrimordialForNid(conceptNid));
final String description = Frills.getDescription(conceptNid,
this.STAMP_COORDINATES,
LanguageCoordinates.getUsEnglishLanguagePreferredTermCoordinate())
.orElse("ERROR!");
if (description.equals("ERROR!")) {
this.log.error("Failed to find any description on concept " +
Get.identifierService().getUuidPrimordialForNid(conceptNid));
}
return description;
}
if (descriptions.size() > 1) {
this.log.warn("Found " + descriptions.size() +
" descriptions flagged as the 'Preferred' vhat type on concept " +
Get.identifierService().getUuidPrimordialForNid(conceptNid));
}
return descriptions.get(0);
}
/**
* Gets the relationships.
*
* @param concept the concept
* @param startDate the start date
* @param endDate the end date
* @return a List of Relationship objects for the concept
*/
private List<Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships.Relationship> getRelationships(
ConceptChronology<?> concept,
long startDate,
long endDate) {
final List<Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships.Relationship> relationships =
new ArrayList<>();
for (final AssociationInstance ai: AssociationUtilities.getSourceAssociations(concept.getNid(),
this.STAMP_COORDINATES)) {
final SememeChronology<?> sc = ai.getData()
.getChronology();
ActionType action = determineAction(sc, startDate, endDate);
final Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships.Relationship xmlRelationship =
new Terminology.CodeSystem.Version.CodedConcepts.CodedConcept.Relationships.Relationship();
try {
String newTargetCode = null;
String oldTargetCode = null;
if (ai.getTargetComponent()
.isPresent()) {
newTargetCode = getCodeFromNid(Get.identifierService()
.getNidForUuids(ai.getTargetComponent()
.get()
.getPrimordialUuid()));
if ((newTargetCode == null) || newTargetCode.isEmpty()) {
this.log.warn("Failed to find new target code for concept " +
ai.getTargetComponent().get().getPrimordialUuid());
}
}
if (action == ActionType.UPDATE) {
// This is an active/inactive change
oldTargetCode = newTargetCode;
newTargetCode = null;
} else if (action != ActionType.ADD) {
// Get the old target value
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<DynamicSememe<?>> coll =
((SememeChronology) sc).getVisibleOrderedVersionList(this.STAMP_COORDINATES);
Collections.reverse(coll);
for (final DynamicSememe<?> s: coll) {
if (s.getTime() < startDate) {
final AssociationInstance assocInst = AssociationInstance.read(s, null);
oldTargetCode = getCodeFromNid(Get.identifierService()
.getNidForUuids(assocInst.getTargetComponent()
.get()
.getPrimordialUuid()));
if ((oldTargetCode == null) || oldTargetCode.isEmpty()) {
this.log.error("Failed to find old target code for concept " +
ai.getTargetComponent().get().getPrimordialUuid());
}
break;
}
}
// if NONE && old != new => UPDATE
if ((newTargetCode != null) &&!newTargetCode.equals(oldTargetCode) && (action == ActionType.NONE)) {
action = ActionType.UPDATE;
}
}
xmlRelationship.setAction(action);
xmlRelationship.setActive(ai.getData()
.getState() == State.ACTIVE);
xmlRelationship.setTypeName(ai.getAssociationType()
.getAssociationName());
xmlRelationship.setOldTargetCode(oldTargetCode);
xmlRelationship.setNewTargetCode(newTargetCode);
if (action != ActionType.NONE) {
relationships.add(xmlRelationship);
}
} catch (final Exception e) {
// Per Dan, catch()-ing to protect against export failure if this were to cause a problem
// as this code is being added very late to Release 2
this.log.error("Association build failure");
}
}
return relationships;
}
/**
* Checks for sememe modified in date range.
*
* @param nid the nid
* @param startDate the start date
* @return true or false, if the sememe was modified in the date range
*/
private boolean hasSememeModifiedInDateRange(int nid, long startDate) {
// Check all the nested sememes
return Get.sememeService()
.getSememesForComponent(nid)
.anyMatch(sc -> {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<LatestVersion<SememeVersion>> sv =
((SememeChronology) sc).getLatestVersion(SememeVersion.class,
this.STAMP_COORDINATES);
if (sv.isPresent()) {
if (sv.get()
.value()
.getTime() > startDate) {
return true;
}
}
// recurse
if (hasSememeModifiedInDateRange(sc.getNid(), startDate)) {
return true;
}
return false;
});
}
}