/* * 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.mapping.data; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; //~--- non-JDK imports -------------------------------------------------------- import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sh.isaac.MetaData; import sh.isaac.api.Get; import sh.isaac.api.LookupService; import sh.isaac.api.State; import sh.isaac.api.chronicle.LatestVersion; import sh.isaac.api.chronicle.ObjectChronology; import sh.isaac.api.chronicle.ObjectChronologyType; import sh.isaac.api.commit.ChangeCheckerMode; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.concept.ConceptVersion; import sh.isaac.api.component.concept.description.DescriptionBuilderService; 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.MutableDescriptionSememe; import sh.isaac.api.component.sememe.version.MutableDynamicSememe; import sh.isaac.api.component.sememe.version.SememeVersion; 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.DynamicSememeDataType; import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription; import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeValidatorType; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeString; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeUUID; import sh.isaac.api.constants.DynamicSememeConstants; import sh.isaac.api.coordinate.EditCoordinate; import sh.isaac.api.coordinate.StampCoordinate; import sh.isaac.mapping.constants.IsaacMappingConstants; import sh.isaac.model.sememe.dataTypes.DynamicSememeStringImpl; import sh.isaac.model.sememe.dataTypes.DynamicSememeUUIDImpl; import sh.isaac.model.sememe.version.DynamicSememeImpl; import sh.isaac.utility.Frills; //~--- classes ---------------------------------------------------------------- /** * {@link MappingSet} * * A Convenience class to hide unnecessary OTF bits from the Mapping APIs. * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ public class MappingSetDAO extends MappingDAO { /** The Constant LOG. */ private static final Logger LOG = LoggerFactory.getLogger(MappingSetDAO.class); //~--- methods ------------------------------------------------------------- /** * Create and store a new mapping set in the DB. * * @param mappingName - The name of the mapping set (used for the FSN and preferred term of the underlying concept) * @param inverseName - (optional) inverse name of the mapping set (if it makes sense for the mapping) * @param purpose - (optional) - user specified purpose of the mapping set * @param description - the intended use of the mapping set * @param editorStatus - (optional) user specified status concept of the mapping set * @param stampCoord the stamp coord * @param editCoord the edit coord * @return the mapping set * @throws IOException Signals that an I/O exception has occurred. */ public static MappingSet createMappingSet(String mappingName, String inverseName, String purpose, String description, UUID editorStatus, StampCoordinate stampCoord, EditCoordinate editCoord) throws IOException { // We need to create a new concept - which itself is defining a dynamic sememe - so set that up here. final DynamicSememeUsageDescription rdud = Frills.createNewDynamicSememeUsageDescriptionConcept(mappingName, mappingName, description, new DynamicSememeColumnInfo[] { new DynamicSememeColumnInfo( 0, DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMN_ASSOCIATION_TARGET_COMPONENT.getUUID(), DynamicSememeDataType.UUID, null, false, false), new DynamicSememeColumnInfo( 1, IsaacMappingConstants.get().DYNAMIC_SEMEME_COLUMN_MAPPING_EQUIVALENCE_TYPE.getUUID(), DynamicSememeDataType.UUID, null, false, DynamicSememeValidatorType.IS_KIND_OF, new DynamicSememeUUIDImpl( IsaacMappingConstants.get().MAPPING_EQUIVALENCE_TYPES.getUUID()), false) }, // new DynamicSememeColumnInfo(2, IsaacMappingConstants.get().MAPPING_STATUS.getUUID(), DynamicSememeDataType.UUID, null, false, // DynamicSememeValidatorType.IS_KIND_OF, new DynamicSememeUUIDImpl(IsaacMappingConstants.get().MAPPING_STATUS.getUUID()), false)}, null, ObjectChronologyType.CONCEPT, null, editCoord); // TODO figure out if I need to be doing this for the indexer // Get.workExecutors().getExecutor().execute(() -> // { // try // { // SememeIndexerConfiguration.configureColumnsToIndex(rdud.getDynamicSememeUsageDescriptorSequence(), new Integer[] {0, 1}, true); // } // catch (Exception e) // { // LOG.error("Unexpected error enabling the index on newly created mapping set!", e); // } // }); // Then, annotate the concept created above as a member of the MappingSet dynamic sememe, and add the inverse name, if present. if (!StringUtils.isBlank(inverseName)) { final ObjectChronology<?> builtDesc = LookupService.get() .getService(DescriptionBuilderService.class) .getDescriptionBuilder(inverseName, rdud.getDynamicSememeUsageDescriptorSequence(), MetaData.SYNONYM, MetaData.ENGLISH_LANGUAGE) .build(editCoord, ChangeCheckerMode.ACTIVE) .getNoThrow(); Get.sememeBuilderService() .getDynamicSememeBuilder(builtDesc.getNid(), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getSequence()) .build(editCoord, ChangeCheckerMode.ACTIVE); } @SuppressWarnings("rawtypes") final SememeChronology mappingAnnotation = Get.sememeBuilderService() .getDynamicSememeBuilder(Get.identifierService() .getConceptNid( rdud.getDynamicSememeUsageDescriptorSequence()), IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_SEMEME_TYPE .getSequence(), new DynamicSememeData[] { // (editorStatus == null ? null : new DynamicSememeUUIDImpl(editorStatus)), (StringUtils.isBlank(purpose) ? null : new DynamicSememeStringImpl(purpose)) }) .build(editCoord, ChangeCheckerMode.ACTIVE) .getNoThrow(); try { Get.commitService() .commit("update mapping item") .get(); } catch (final Exception e) { throw new RuntimeException(); } @SuppressWarnings("unchecked") final Optional<LatestVersion<DynamicSememe<?>>> sememe = mappingAnnotation.getLatestVersion(DynamicSememe.class, stampCoord); // Find the constructed dynamic refset return new MappingSet(sememe.get().value(), stampCoord); } /** * Retire mapping set. * * @param mappingSetPrimordialUUID the mapping set primordial UUID * @param stampCoord the stamp coord * @param editCoord the edit coord * @throws IOException Signals that an I/O exception has occurred. */ public static void retireMappingSet(UUID mappingSetPrimordialUUID, StampCoordinate stampCoord, EditCoordinate editCoord) throws IOException { setConceptStatus(mappingSetPrimordialUUID, State.INACTIVE, stampCoord, editCoord); } /** * Un retire mapping set. * * @param mappingSetPrimordialUUID the mapping set primordial UUID * @param stampCoord the stamp coord * @param editCoord the edit coord * @throws IOException Signals that an I/O exception has occurred. */ public static void unRetireMappingSet(UUID mappingSetPrimordialUUID, StampCoordinate stampCoord, EditCoordinate editCoord) throws IOException { setConceptStatus(mappingSetPrimordialUUID, State.ACTIVE, stampCoord, editCoord); } /** * Store the changes (done via set methods) on the passed in mapping set. * * @param mappingSet - The mappingSet that carries the changes * @param stampCoord the stamp coord * @param editCoord the edit coord * @throws RuntimeException the runtime exception */ @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) public static void updateMappingSet(MappingSet mappingSet, StampCoordinate stampCoord, EditCoordinate editCoord) throws RuntimeException { final ConceptChronology mappingConcept = Get.conceptService() .getConcept(mappingSet.getPrimordialUUID()); Get.sememeService().getSememesForComponent(mappingConcept.getNid()).filter(s -> s.getSememeType() == SememeType.DESCRIPTION).forEach(descriptionC -> { final Optional<LatestVersion<DescriptionSememe<?>>> latest = ((SememeChronology) descriptionC).getLatestVersion(DescriptionSememe.class, stampCoord); if (latest.isPresent()) { final DescriptionSememe<?> ds = latest.get() .value(); if (ds.getDescriptionTypeConceptSequence() == MetaData.SYNONYM.getConceptSequence()) { if (Frills.isDescriptionPreferred(ds.getNid(), null)) { if (!ds.getText() .equals(mappingSet.getName())) { final MutableDescriptionSememe mutable = ((SememeChronology<DescriptionSememe>) ds.getChronology()).createMutableVersion( MutableDescriptionSememe.class, ds.getStampSequence()); mutable.setCaseSignificanceConceptSequence(ds.getCaseSignificanceConceptSequence()); mutable.setDescriptionTypeConceptSequence(ds.getDescriptionTypeConceptSequence()); mutable.setLanguageConceptSequence(ds.getLanguageConceptSequence()); mutable.setText(mappingSet.getName()); Get.commitService() .addUncommitted(ds.getChronology()); } } else // see if it is the inverse name { if (Get.sememeService() .getSememesForComponentFromAssemblage(ds.getNid(), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getSequence()) .anyMatch(sememeC -> { return sememeC.isLatestVersionActive(stampCoord); })) { if (!ds.getText() .equals(mappingSet.getInverseName())) { final MutableDescriptionSememe mutable = ((SememeChronology<DescriptionSememe>) ds.getChronology()).createMutableVersion( MutableDescriptionSememe.class, ds.getStampSequence()); mutable.setCaseSignificanceConceptSequence(ds.getCaseSignificanceConceptSequence()); mutable.setDescriptionTypeConceptSequence(ds.getDescriptionTypeConceptSequence()); mutable.setLanguageConceptSequence(ds.getLanguageConceptSequence()); mutable.setText(mappingSet.getInverseName()); Get.commitService() .addUncommitted(ds.getChronology()); } } } } else if (ds.getDescriptionTypeConceptSequence() == MetaData.DEFINITION_DESCRIPTION_TYPE.getConceptSequence()) { if (Frills.isDescriptionPreferred(ds.getNid(), null)) { if (!mappingSet.getDescription() .equals(ds.getText())) { final MutableDescriptionSememe mutable = ((SememeChronology<DescriptionSememe>) ds.getChronology()).createMutableVersion( MutableDescriptionSememe.class, ds.getStampSequence()); mutable.setCaseSignificanceConceptSequence(ds.getCaseSignificanceConceptSequence()); mutable.setDescriptionTypeConceptSequence(ds.getDescriptionTypeConceptSequence()); mutable.setLanguageConceptSequence(ds.getLanguageConceptSequence()); mutable.setText(mappingSet.getDescription()); Get.commitService() .addUncommitted(ds.getChronology()); } } } } }); final Optional<SememeChronology<? extends SememeVersion<?>>> mappingSememe = Get.sememeService() .getSememesForComponentFromAssemblage( mappingConcept.getNid(), IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_SEMEME_TYPE .getSequence()) .findAny(); if (!mappingSememe.isPresent()) { LOG.error("Couldn't find mapping refex?"); throw new RuntimeException("internal error"); } final Optional<LatestVersion<DynamicSememe<?>>> latestVersion = ((SememeChronology) mappingSememe.get()).getLatestVersion(DynamicSememe.class, stampCoord.makeAnalog(State.ACTIVE, State.INACTIVE)); final DynamicSememe<?> latest = latestVersion.get() .value(); if (((latest.getData()[0] == null) && (mappingSet.getPurpose() != null)) || ((mappingSet.getPurpose() == null) && (latest.getData()[0] != null)) || ((latest.getData()[0] != null) && (latest.getData()[0] instanceof DynamicSememeUUID) && ((DynamicSememeUUID) latest.getData()[0]).getDataUUID().equals(mappingSet.getEditorStatusConcept())) || ((latest.getData()[1] == null) && (mappingSet.getPurpose() != null)) || ((mappingSet.getPurpose() == null) && (latest.getData()[1] != null)) || ((latest.getData()[1] != null) && (latest.getData()[1] instanceof DynamicSememeString) && ((DynamicSememeString) latest.getData()[1]).getDataString().equals(mappingSet.getPurpose()))) { final DynamicSememeImpl mutable = (DynamicSememeImpl) ((SememeChronology) mappingSememe.get()).createMutableVersion( MutableDynamicSememe.class, latest.getStampSequence()); mutable.setData(new DynamicSememeData[] { ((mappingSet.getEditorStatusConcept() == null) ? null : new DynamicSememeUUIDImpl(mappingSet.getEditorStatusConcept())), (StringUtils.isBlank( mappingSet.getPurpose()) ? null : new DynamicSememeStringImpl(mappingSet.getPurpose())) }); Get.commitService() .addUncommitted(latest.getChronology()); } Get.commitService() .commit("Update mapping"); } //~--- get methods --------------------------------------------------------- /** * Gets the mapping concept. * * @param sememe the sememe * @param stampCoord the stamp coord * @return the mapping concept * @throws RuntimeException the runtime exception */ public static Optional<ConceptVersion<?>> getMappingConcept(DynamicSememe<?> sememe, StampCoordinate stampCoord) throws RuntimeException { final ConceptChronology<? extends ConceptVersion<?>> cc = Get.conceptService() .getConcept(sememe.getReferencedComponentNid()); @SuppressWarnings({ "rawtypes", "unchecked" }) final Optional<LatestVersion<ConceptVersion<?>>> cv = ((ConceptChronology) cc).getLatestVersion(ConceptVersion.class, stampCoord.makeAnalog(State.ACTIVE, State.INACTIVE)); if (cv.isPresent()) { if (cv.get() .contradictions() .isPresent()) { // TODO handle these properly LOG.warn("Concept has contradictions!"); } return Optional.of(cv.get() .value()); } return Optional.empty(); } /** * Gets the mapping sets. * * @param stampCoord the stamp coord * @return the mapping sets * @throws IOException Signals that an I/O exception has occurred. */ public static List<MappingSet> getMappingSets(StampCoordinate stampCoord) throws IOException { final ArrayList<MappingSet> result = new ArrayList<>(); Get.sememeService() .getSememesFromAssemblage(IsaacMappingConstants.get().DYNAMIC_SEMEME_MAPPING_SEMEME_TYPE .getSequence()) .forEach(sememeC -> { @SuppressWarnings({ "unchecked", "rawtypes" }) final Optional<LatestVersion<DynamicSememe<?>>> latest = ((SememeChronology) sememeC).getLatestVersion(DynamicSememe.class, stampCoord); if (latest.isPresent()) { // TODO handle contradictions properly result.add(new MappingSet(latest.get().value(), stampCoord)); if (latest.get() .contradictions() .isPresent()) { latest.get() .contradictions() .get() .forEach((contradiction) -> result.add(new MappingSet(contradiction, stampCoord))); } } }); return result; } }