/* * 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.converters.sharedUtils; //~--- JDK imports ------------------------------------------------------------ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.UUID; //~--- non-JDK imports -------------------------------------------------------- import org.apache.commons.lang3.StringUtils; import org.codehaus.plexus.util.FileUtils; import sh.isaac.MetaData; import sh.isaac.api.DataTarget; import sh.isaac.api.Get; import sh.isaac.api.LookupService; import sh.isaac.api.State; import sh.isaac.api.bootstrap.TermAux; import sh.isaac.api.chronicle.ObjectChronology; import sh.isaac.api.chronicle.ObjectChronologyType; import sh.isaac.api.collections.ConceptSequenceSet; import sh.isaac.api.collections.UuidIntMapMap; import sh.isaac.api.component.concept.ConceptBuilderService; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.concept.ConceptSpecification; import sh.isaac.api.component.concept.ConceptVersion; import sh.isaac.api.component.sememe.SememeBuilder; import sh.isaac.api.component.sememe.SememeBuilderService; import sh.isaac.api.component.sememe.SememeChronology; import sh.isaac.api.component.sememe.SememeType; import sh.isaac.api.component.sememe.version.ComponentNidSememe; import sh.isaac.api.component.sememe.version.DescriptionSememe; import sh.isaac.api.component.sememe.version.DynamicSememe; import sh.isaac.api.component.sememe.version.LogicGraphSememe; 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.DynamicSememeDataType; import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUtility; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeArray; import sh.isaac.api.constants.Constants; import sh.isaac.api.constants.DynamicSememeConstants; import sh.isaac.api.coordinate.StampCoordinate; import sh.isaac.api.coordinate.StampPosition; import sh.isaac.api.coordinate.StampPrecedence; import sh.isaac.api.externalizable.DataWriterService; import sh.isaac.api.externalizable.MultipleDataWriterService; import sh.isaac.api.externalizable.OchreExternalizable; import sh.isaac.api.identity.StampedVersion; import sh.isaac.api.logic.LogicalExpression; import sh.isaac.api.logic.LogicalExpressionBuilder; import sh.isaac.api.logic.LogicalExpressionBuilderService; import sh.isaac.api.logic.assertions.ConceptAssertion; import sh.isaac.api.util.ChecksumGenerator; import sh.isaac.api.util.UuidT5Generator; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_Associations; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_Descriptions; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_DualParentPropertyType; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_Refsets; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_Relations; import sh.isaac.converters.sharedUtils.propertyTypes.BPT_Skip; import sh.isaac.converters.sharedUtils.propertyTypes.Property; import sh.isaac.converters.sharedUtils.propertyTypes.PropertyAssociation; import sh.isaac.converters.sharedUtils.propertyTypes.PropertyType; import sh.isaac.converters.sharedUtils.propertyTypes.ValuePropertyPair; import sh.isaac.converters.sharedUtils.stats.ConverterUUID; import sh.isaac.converters.sharedUtils.stats.LoadStats; import sh.isaac.model.concept.ConceptChronologyImpl; import sh.isaac.model.configuration.LogicCoordinates; import sh.isaac.model.coordinate.StampCoordinateImpl; import sh.isaac.model.coordinate.StampPositionImpl; import sh.isaac.model.sememe.dataTypes.DynamicSememeStringImpl; import sh.isaac.model.sememe.dataTypes.DynamicSememeUUIDImpl; import static sh.isaac.api.logic.LogicalExpressionBuilder.And; import static sh.isaac.api.logic.LogicalExpressionBuilder.ConceptAssertion; import static sh.isaac.api.logic.LogicalExpressionBuilder.NecessarySet; //~--- classes ---------------------------------------------------------------- /** * * {@link IBDFCreationUtility} * * Various constants and methods for building ISAAC terminology content, and writing it directly * to an IBDF file rather than a database. * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ public class IBDFCreationUtility { /** The Constant METADATA_SEMANTIC_TAG. */ public final static String METADATA_SEMANTIC_TAG = " (ISAAC)"; /** The read back stamp. */ protected static StampCoordinate readBackStamp; //~--- fields -------------------------------------------------------------- /** The module. */ private ComponentReference module = null; /** The refset allowed column types. */ private final HashMap<UUID, DynamicSememeColumnInfo[]> refexAllowedColumnTypes = new HashMap<>(); /** The concept has stated graph. */ private final HashSet<UUID> conceptHasStatedGraph = new HashSet<>(); /** The concept has inferred graph. */ private final HashSet<UUID> conceptHasInferredGraph = new HashSet<>(); /** The load statistics. */ private LoadStats ls = new LoadStats(); /** The author concept sequence. */ private final int authorSeq; /** The terminology path sequence. */ private final int terminologyPathSeq; /** The default time. */ private final long defaultTime; /** The concept builder service. */ private final ConceptBuilderService conceptBuilderService; /** The expression builder service. */ private final LogicalExpressionBuilderService expressionBuilderService; /** The sememe builder service. */ private final SememeBuilderService<?> sememeBuilderService; /** The writer. */ private final DataWriterService writer; //~--- constructors -------------------------------------------------------- /** * Creates and stores the path concept - sets up the various namespace details. * If creating a module per version, you should specify both module parameters - for the version specific module to create, and the parent grouping module. * The namespace will be specified based on the parent grouping module. * * @param moduleToCreate - if present, a new concept will be created, using this value as the FSN / preferred term for use as the module * @param preExistingModule - if moduleToCreate is not present, lookup the concept with this UUID to use as the module. if moduleToCreate is present * use preExistingModule as the parent concept for the moduleToCreate, rather than the default of MODULE. * @param outputDirectory - The path to write the output files to * @param outputArtifactId - Combined with outputArtifactClassifier and outputArtifactVersion to name the final ibdf file * @param outputArtifactVersion - Combined with outputArtifactClassifier and outputArtifactId to name the final ibdf file * @param outputArtifactClassifier - optional - Combined with outputArtifactId and outputArtifactVersion to name the final ibdf file * @param outputJson - true to dump out the data in gson format for debug * @param defaultTime - the timestamp to place on created elements, when no other timestamp is specified on the element itself. * @throws Exception the exception */ public IBDFCreationUtility(Optional<String> moduleToCreate, Optional<ConceptSpecification> preExistingModule, File outputDirectory, String outputArtifactId, String outputArtifactVersion, String outputArtifactClassifier, boolean outputJson, long defaultTime) throws Exception { UuidIntMapMap.NID_TO_UUID_CACHE_SIZE = 5000000; final File file = new File(outputDirectory, "isaac-db"); // make sure this is empty FileUtils.deleteDirectory(file); System.setProperty(Constants.DATA_STORE_ROOT_LOCATION_PROPERTY, file.getCanonicalPath()); LookupService.startupIsaac(); // Initialize after starting up isaac... this.authorSeq = MetaData.USER.getConceptSequence(); this.terminologyPathSeq = MetaData.DEVELOPMENT_PATH.getConceptSequence(); // TODO automate this somehow.... registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENSION_DEFINITION .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENSION_DEFINITION .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_DEFINITION_DESCRIPTION .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_DEFINITION_DESCRIPTION .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_INDEX_CONFIGURATION .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_INDEX_CONFIGURATION .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_COMMENT_ATTRIBUTE .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_COMMENT_ATTRIBUTE .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_DESCRIPTION_TYPE .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_DESCRIPTION_TYPE .getDynamicSememeColumns()); registerDynamicSememeColumnInfo( DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_RELATIONSHIP_TYPE .getUUID(), DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_RELATIONSHIP_TYPE .getDynamicSememeColumns()); // TODO figure out how to get rid of this copy/paste mess too registerDynamicSememeColumnInfo( MetaData.LOINC_NUM.getPrimordialUuid(), new DynamicSememeColumnInfo[] { new DynamicSememeColumnInfo( 0, DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMN_VALUE.getPrimordialUuid(), DynamicSememeDataType.STRING, null, true, true) }); this.conceptBuilderService = Get.conceptBuilderService(); this.conceptBuilderService.setDefaultLanguageForDescriptions(MetaData.ENGLISH_LANGUAGE); this.conceptBuilderService.setDefaultDialectAssemblageForDescriptions(MetaData.US_ENGLISH_DIALECT); this.conceptBuilderService.setDefaultLogicCoordinate(LogicCoordinates.getStandardElProfile()); this.expressionBuilderService = Get.logicalExpressionBuilderService(); this.sememeBuilderService = Get.sememeBuilderService(); this.defaultTime = defaultTime; final StampPosition stampPosition = new StampPositionImpl( Long.MAX_VALUE, MetaData.DEVELOPMENT_PATH.getConceptSequence()); readBackStamp = new StampCoordinateImpl( StampPrecedence.PATH, stampPosition, ConceptSequenceSet.EMPTY, State.ANY_STATE_SET); final UUID moduleUUID = moduleToCreate.isPresent() ? UuidT5Generator.get( UuidT5Generator.PATH_ID_FROM_FS_DESC, moduleToCreate.get()) : preExistingModule.get() .getPrimordialUuid(); // tack the version onto the end of the ibdf file, so that when multiple ibdf files for a single type of content, such as // loinc 2.52, loinc 2.54 - we don't have a file name collision during the ibdf build. final String outputName = outputArtifactId + "-" + outputArtifactVersion + (StringUtils.isBlank(outputArtifactClassifier) ? "" : "-" + outputArtifactClassifier); this.writer = new MultipleDataWriterService( outputJson ? Optional.of(new File(outputDirectory, outputName + ".json").toPath()) : Optional.empty(), Optional.of(new File(outputDirectory, outputName + ".ibdf").toPath())); if (moduleToCreate.isPresent()) { this.module = ComponentReference.fromConcept(moduleUUID); createConcept( moduleUUID, moduleToCreate.get(), true, preExistingModule.isPresent() ? preExistingModule.get() .getPrimordialUuid() : MetaData.MODULE.getPrimordialUuid()); } else { this.module = ComponentReference.fromConcept( preExistingModule.get() .getPrimordialUuid(), preExistingModule.get() .getConceptSequence()); } ConsoleUtil.println( "Loading with module '" + this.module.getPrimordialUuid() + "' (" + this.module.getNid() + ") on DEVELOPMENT path"); } //~--- enums --------------------------------------------------------------- /** * The Enum DescriptionType. */ public static enum DescriptionType { /** Fully specified name. */ FSN, /** Synonym. */ SYNONYM, /** Definition. */ DEFINITION; /** * Convert a UUID to a description type. * * @param typeUuid the type id * @return the description type */ public static DescriptionType convert(UUID typeUuid) { if (MetaData.FULLY_SPECIFIED_NAME.getPrimordialUuid() .equals(typeUuid)) { return FSN; } else if (MetaData.SYNONYM.getPrimordialUuid() .equals(typeUuid)) { return SYNONYM; } if (MetaData.DEFINITION_DESCRIPTION_TYPE.getPrimordialUuid() .equals(typeUuid)) { return DEFINITION; } throw new RuntimeException("Unknown description type for UUID: " + typeUuid); } //~--- get methods ------------------------------------------------------ /** * Gets the concept spec. * * @return the concept spec */ public ConceptSpecification getConceptSpec() { switch (this) { case FSN: return MetaData.FULLY_SPECIFIED_NAME; case SYNONYM: return MetaData.SYNONYM; case DEFINITION: return MetaData.DEFINITION_DESCRIPTION_TYPE; default: throw new RuntimeException("Unsupported descriptiontype '" + this + "'"); } } } ; //~--- methods ------------------------------------------------------------- /** * Adds the annotation. * * @param referencedComponent The component to attach this annotation to * @param uuidForCreatedAnnotation - the UUID to use for the created annotation. If null, generated from uuidForCreatedAnnotation, value, refexDynamicTypeUuid * @param value - the value to attach (may be null if the annotation only serves to mark 'membership') - columns must align with values specified in the definition * of the sememe represented by refexDynamicTypeUuid * @param refexDynamicTypeUuid - the uuid of the dynamic sememe type - * @param state - state or null (for active) * @param time - if null, uses the component time * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addAnnotation(ComponentReference referencedComponent, UUID uuidForCreatedAnnotation, DynamicSememeData value, UUID refexDynamicTypeUuid, State state, Long time) { return addAnnotation(referencedComponent, uuidForCreatedAnnotation, ((value == null) ? new DynamicSememeData[] {} : new DynamicSememeData[] { value }), refexDynamicTypeUuid, state, time, null); } /** * Adds the annotation. * * @param referencedComponent The component to attach this annotation to * @param uuidForCreatedAnnotation - the UUID to use for the created annotation. If null, generated from uuidForCreatedAnnotation, value, refexDynamicTypeUuid * @param values - the values to attach (may be null if the annotation only serves to mark 'membership') - columns must align with values specified in the definition * of the sememe represented by refexDynamicTypeUuid * @param refexDynamicTypeUuid - the uuid of the dynamic sememe type - * @param state - state or null (for active) * @param time - if null, uses the component time * @param module the module * @return the sememe chronology */ @SuppressWarnings("unchecked") public SememeChronology<DynamicSememe<?>> addAnnotation(ComponentReference referencedComponent, UUID uuidForCreatedAnnotation, DynamicSememeData[] values, UUID refexDynamicTypeUuid, State state, Long time, UUID module) { validateDataTypes(refexDynamicTypeUuid, values); @SuppressWarnings("rawtypes") final SememeBuilder sb = this.sememeBuilderService.getDynamicSememeBuilder( referencedComponent.getNid(), Get.identifierService() .getConceptSequenceForUuids(refexDynamicTypeUuid), values); if (uuidForCreatedAnnotation == null) { final StringBuilder temp = new StringBuilder(); temp.append(refexDynamicTypeUuid.toString()); temp.append(referencedComponent.getPrimordialUuid() .toString()); if (values != null) { for (final DynamicSememeData d: values) { if (d == null) { temp.append("null"); } else { temp.append(d.getDynamicSememeDataType() .getDisplayName()); temp.append(ChecksumGenerator.calculateChecksum("SHA1", d.getData())); } } } uuidForCreatedAnnotation = ConverterUUID.createNamespaceUUIDFromString(temp.toString()); } sb.setPrimordialUuid(uuidForCreatedAnnotation); final ArrayList<OchreExternalizable> builtObjects = new ArrayList<>(); final SememeChronology<DynamicSememe<?>> sc = (SememeChronology<DynamicSememe<?>>) sb.build( createStamp( state, selectTime(time, referencedComponent), module), builtObjects); for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } if ((values == null) || (values.length == 0)) { this.ls.addRefsetMember(getOriginStringForUuid(refexDynamicTypeUuid)); } else { if (BPT_Associations.isAssociation(refexDynamicTypeUuid)) { this.ls.addAssociation(getOriginStringForUuid(refexDynamicTypeUuid)); } else { this.ls.addAnnotation( ((referencedComponent.getTypeString() .length() == 0) ? getOriginStringForUuid(referencedComponent.getPrimordialUuid()) : referencedComponent.getTypeString()), getOriginStringForUuid(refexDynamicTypeUuid)); } } return sc; } /** * Add an association. The source of the association is assumed to be the specified concept. * * @param concept the concept * @param associationPrimordialUuid - optional - if not provided, created from the source, target and type. * @param targetUuid the target uuid * @param associationTypeUuid required * @param state the state * @param time - if null, default is used * @param module the module * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addAssociation(ComponentReference concept, UUID associationPrimordialUuid, UUID targetUuid, UUID associationTypeUuid, State state, Long time, UUID module) { if (!isConfiguredAsDynamicSememe(associationTypeUuid)) { ConsoleUtil.printErrorln( "Asked to create an association with an unregistered association type. This is deprecated, and should be fixed..."); configureConceptAsAssociation(associationTypeUuid, null); } return addAnnotation( concept, associationPrimordialUuid, new DynamicSememeData[] { new DynamicSememeUUIDImpl(targetUuid) }, associationTypeUuid, state, time, module); } /** * Add a description to the concept. UUID for the description is calculated from the target concept, description value, type, and preferred flag. * * @param concept the concept * @param descriptionValue the description value * @param wbDescriptionType the wb description type * @param preferred the preferred * @param sourceDescriptionTypeUUID the source description type UUID * @param state the state * @return the sememe chronology */ public SememeChronology<DescriptionSememe<?>> addDescription(ComponentReference concept, String descriptionValue, DescriptionType wbDescriptionType, boolean preferred, UUID sourceDescriptionTypeUUID, State state) { return addDescription( concept, null, descriptionValue, wbDescriptionType, preferred, null, null, null, null, sourceDescriptionTypeUUID, state, null); } /** * Add a description to the concept. * * @param concept the concept * @param descriptionPrimordialUUID the description primordial UUID * @param descriptionValue the description value * @param wbDescriptionType the wb description type * @param preferred the preferred * @param sourceDescriptionTypeUUID the source description type UUID * @param status the status * @return the sememe chronology */ public SememeChronology<DescriptionSememe<?>> addDescription(ComponentReference concept, UUID descriptionPrimordialUUID, String descriptionValue, DescriptionType wbDescriptionType, boolean preferred, UUID sourceDescriptionTypeUUID, State status) { return addDescription( concept, descriptionPrimordialUUID, descriptionValue, wbDescriptionType, preferred, null, null, null, null, sourceDescriptionTypeUUID, status, null); } /** * Add a description to the concept. * * @param concept - the concept to add this description to * @param descriptionPrimordialUUID - if not supplied, created from the concept UUID and the description value and description type * @param descriptionValue - the text value * @param wbDescriptionType - the type of the description * @param preferred - true, false, or null to not create any acceptability entry see {@link #addDescriptionAcceptibility()} * @param dialect - ignored if @param preferred is set to null. if null, defaults to {@link MetaData#US_ENGLISH_DIALECT} * @param caseSignificant - if null, defaults to {@link MetaData#DESCRIPTION_NOT_CASE_SENSITIVE} * @param languageCode - if null, uses {@link MetaData#ENGLISH_LANGUAGE} * @param module - if null, uses the default from the EConceptUtility instance * @param sourceDescriptionTypeUUID - this optional value is attached as the extended description type * @param state active / inactive * @param time - defaults to concept time * @return the sememe chronology */ @SuppressWarnings("unchecked") public SememeChronology<DescriptionSememe<?>> addDescription(ComponentReference concept, UUID descriptionPrimordialUUID, String descriptionValue, DescriptionType wbDescriptionType, Boolean preferred, UUID dialect, UUID caseSignificant, UUID languageCode, UUID module, UUID sourceDescriptionTypeUUID, State state, Long time) { if (descriptionValue == null) { throw new RuntimeException("Description value is required"); } if (dialect == null) { dialect = MetaData.US_ENGLISH_DIALECT.getPrimordialUuid(); } if (languageCode == null) { languageCode = MetaData.ENGLISH_LANGUAGE.getPrimordialUuid(); } if (descriptionPrimordialUUID == null) { descriptionPrimordialUUID = ConverterUUID.createNamespaceUUIDFromStrings( concept.getPrimordialUuid() .toString(), descriptionValue, wbDescriptionType.name(), dialect.toString(), languageCode.toString(), (preferred == null) ? "null" : preferred.toString()); } @SuppressWarnings({ "rawtypes" }) final SememeBuilder<? extends SememeChronology<? extends DescriptionSememe>> descBuilder = this.sememeBuilderService.getDescriptionSememeBuilder( Get.identifierService() .getConceptSequenceForUuids( (caseSignificant == null) ? MetaData.DESCRIPTION_NOT_CASE_SENSITIVE.getPrimordialUuid() : caseSignificant), Get.identifierService() .getConceptSequenceForUuids( languageCode), wbDescriptionType.getConceptSpec() .getConceptSequence(), descriptionValue, concept.getNid()); descBuilder.setPrimordialUuid(descriptionPrimordialUUID); final List<ObjectChronology<? extends StampedVersion>> builtObjects = new ArrayList<>(); final SememeChronology<DescriptionSememe<?>> newDescription = (SememeChronology<DescriptionSememe<?>>) descBuilder.build( createStamp( state, selectTime(time, concept), module), builtObjects); if (preferred == null) { // noop } else { final SememeBuilder<?> acceptabilityTypeBuilder = this.sememeBuilderService.getComponentSememeBuilder( preferred ? TermAux.PREFERRED.getNid() : TermAux.ACCEPTABLE.getNid(), newDescription.getNid(), Get.identifierService() .getConceptSequenceForUuids(dialect)); final UUID acceptabilityTypePrimordialUUID = ConverterUUID.createNamespaceUUIDFromStrings( descriptionPrimordialUUID.toString(), dialect.toString()); acceptabilityTypeBuilder.setPrimordialUuid(acceptabilityTypePrimordialUUID); acceptabilityTypeBuilder.build(createStamp(state, selectTime(time, concept), module), builtObjects); this.ls.addAnnotation("Description", getOriginStringForUuid(dialect)); } for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } this.ls.addDescription(wbDescriptionType.name() + ((sourceDescriptionTypeUUID == null) ? "" : ":" + getOriginStringForUuid(sourceDescriptionTypeUUID))); if (sourceDescriptionTypeUUID != null) { addAnnotation( ComponentReference.fromChronology(newDescription, () -> "Description"), null, ((sourceDescriptionTypeUUID == null) ? null : new DynamicSememeUUIDImpl(sourceDescriptionTypeUUID)), DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_DESCRIPTION_TYPE .getPrimordialUuid(), null, null); } return newDescription; } /** * Add a description to the concept. * * @param description the description * @param acceptabilityPrimordialUUID - if not supplied, created from the description UUID, dialectRefsetg and preferred flag * @param dialectRefset - A UUID for a refset like MetaData.US_ENGLISH_DIALECT * @param preferred - true for preferred, false for acceptable * @param state - * @param time - if null, uses the description time * @param module - optional * @return the sememe chronology */ public SememeChronology<ComponentNidSememe<?>> addDescriptionAcceptibility(ComponentReference description, UUID acceptabilityPrimordialUUID, UUID dialectRefset, boolean preferred, State state, Long time, UUID module) { final SememeBuilder sb = this.sememeBuilderService.getComponentSememeBuilder( preferred ? TermAux.PREFERRED.getNid() : TermAux.ACCEPTABLE.getNid(), description.getNid(), Get.identifierService() .getConceptSequenceForUuids(dialectRefset)); if (acceptabilityPrimordialUUID == null) { // TODO not sure if preferred should be part of UUID acceptabilityPrimordialUUID = ConverterUUID.createNamespaceUUIDFromStrings( description.getPrimordialUuid() .toString(), dialectRefset.toString(), preferred + ""); } sb.setPrimordialUuid(acceptabilityPrimordialUUID); final ArrayList<OchreExternalizable> builtObjects = new ArrayList<>(); @SuppressWarnings("unchecked") final SememeChronology<ComponentNidSememe<?>> sc = (SememeChronology<ComponentNidSememe<?>>) sb.build( createStamp(state, selectTime(time, description), module), builtObjects); for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } this.ls.addAnnotation("Description", getOriginStringForUuid(dialectRefset)); return sc; } /** * Add a batch of WB descriptions, following WB rules in always generating a FSN (picking the value based on the propertySubType order). * And then adding other types as specified by the propertySubType value, setting preferred / acceptable according to their ranking. * * @param concept the concept * @param descriptions the descriptions * @return the list */ public List<SememeChronology<DescriptionSememe<?>>> addDescriptions(ComponentReference concept, List<? extends ValuePropertyPair> descriptions) { final ArrayList<SememeChronology<DescriptionSememe<?>>> result = new ArrayList<>(descriptions.size()); Collections.sort(descriptions); boolean haveFSN = false; boolean havePreferredSynonym = false; boolean havePreferredDefinition = false; for (final ValuePropertyPair vpp: descriptions) { DescriptionType descriptionType = null; boolean preferred; if (!haveFSN) { descriptionType = DescriptionType.FSN; preferred = true; haveFSN = true; } else { if (vpp.getProperty() .getPropertySubType() < BPT_Descriptions.SYNONYM) { descriptionType = DescriptionType.FSN; preferred = false; // true case is handled above } else if ((vpp.getProperty().getPropertySubType() >= BPT_Descriptions.SYNONYM) && ((vpp.getProperty().getPropertySubType() < BPT_Descriptions.DEFINITION) || (vpp.getProperty().getPropertySubType() == Integer.MAX_VALUE))) { descriptionType = DescriptionType.SYNONYM; if (!havePreferredSynonym) { preferred = true; havePreferredSynonym = true; } else { preferred = false; } } else if (vpp.getProperty() .getPropertySubType() >= BPT_Descriptions.DEFINITION) { descriptionType = DescriptionType.DEFINITION; if (!havePreferredDefinition) { preferred = true; havePreferredDefinition = true; } else { preferred = false; } } else { throw new RuntimeException("Unexpected error"); } } if (!(vpp.getProperty() .getPropertyType() instanceof BPT_Descriptions)) { throw new RuntimeException( "This method requires properties that have a parent that are an instance of BPT_Descriptions"); } result.add( addDescription( concept, vpp.getUUID(), vpp.getValue(), descriptionType, preferred, null, null, null, null, vpp.getProperty() .getUUID(), (vpp.isDisabled() ? State.INACTIVE : State.ACTIVE), vpp.getTime())); } return result; } /** * Add a workbench official "Fully Specified Name". Convenience method for adding a description of type FSN * * @param concept the concept * @param fullySpecifiedName the fully specified name * @return the sememe chronology */ public SememeChronology<DescriptionSememe<?>> addFullySpecifiedName(ComponentReference concept, String fullySpecifiedName) { return addDescription(concept, fullySpecifiedName, DescriptionType.FSN, true, null, State.ACTIVE); } /** * Add an IS_A_REL relationship, with the time set to now. * Can only be called once per concept. * * @param concept the concept * @param targetUuid the target uuid * @return the sememe chronology */ public SememeChronology<LogicGraphSememe<?>> addParent(ComponentReference concept, UUID targetUuid) { return addParent(concept, null, new UUID[] { targetUuid }, null, null); } /** * This rel add method handles the advanced cases where a rel type 'foo' is actually being loaded as "is_a" (or some other arbitrary type) * it makes the swap, and adds the second value as a UUID annotation on the created relationship. * Can only be called once per concept * * @param concept the concept * @param targetUuid the target uuid * @param p the p * @param time the time * @return the sememe chronology */ public SememeChronology<LogicGraphSememe<?>> addParent(ComponentReference concept, UUID targetUuid, Property p, Long time) { if (p.getWBTypeUUID() == null) { return addParent(concept, null, new UUID[] { targetUuid }, null, time); } else { return addParent(concept, null, new UUID[] { targetUuid }, p.getUUID(), time); } } /** * Add a parent (is a ) relationship. The source of the relationship is assumed to be the specified concept. * Can only be called once per concept * * @param concept the concept * @param relPrimordialUuid - optional - if not provided, created from the source, target and type. * @param targetUuid the target uuid * @param sourceRelTypeUUID the source rel type UUID * @param time - if null, default is used * @return the sememe chronology */ public SememeChronology<LogicGraphSememe<?>> addParent(ComponentReference concept, UUID relPrimordialUuid, UUID[] targetUuid, UUID sourceRelTypeUUID, Long time) { if (this.conceptHasStatedGraph.contains(concept.getPrimordialUuid())) { throw new RuntimeException( "Can only call addParent once! Must utilize addRelationshipGraph for more complex objects. " + "Parents: " + Arrays.toString( targetUuid) + " Child: " + concept.getPrimordialUuid()); } this.conceptHasStatedGraph.add(concept.getPrimordialUuid()); final LogicalExpressionBuilder leb = this.expressionBuilderService.getLogicalExpressionBuilder(); // We are only building isA here, choose necessary set over sufficient. final ConceptAssertion[] cas = new ConceptAssertion[targetUuid.length]; for (int i = 0; i < targetUuid.length; i++) { cas[i] = ConceptAssertion(Get.identifierService() .getConceptSequenceForUuids(targetUuid[i]), leb); } NecessarySet(And(cas)); final LogicalExpression logicalExpression = leb.build(); @SuppressWarnings("rawtypes") final SememeBuilder sb = this.sememeBuilderService.getLogicalExpressionSememeBuilder( logicalExpression, concept.getNid(), this.conceptBuilderService.getDefaultLogicCoordinate() .getStatedAssemblageSequence()); sb.setPrimordialUuid( (relPrimordialUuid != null) ? relPrimordialUuid : ConverterUUID.createNamespaceUUIDFromStrings( concept.getPrimordialUuid() .toString(), Arrays.toString(targetUuid), MetaData.IS_A.getPrimordialUuid() .toString())); final ArrayList<OchreExternalizable> builtObjects = new ArrayList<>(); @SuppressWarnings("unchecked") final SememeChronology<LogicGraphSememe<?>> sci = (SememeChronology<LogicGraphSememe<?>>) sb.build( createStamp(State.ACTIVE, selectTime(time, concept)), builtObjects); for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } if (sourceRelTypeUUID != null) { addUUIDAnnotation( ComponentReference.fromChronology(sci, () -> "Graph"), sourceRelTypeUUID, DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENDED_RELATIONSHIP_TYPE .getPrimordialUuid()); this.ls.addRelationship( getOriginStringForUuid( MetaData.IS_A.getPrimordialUuid()) + ":" + getOriginStringForUuid(sourceRelTypeUUID)); } else { this.ls.addRelationship(getOriginStringForUuid(MetaData.IS_A.getPrimordialUuid())); } return sci; } /** * Adds the refset membership. * * @param referencedComponent the referenced component * @param refexDynamicTypeUuid the refex dynamic type uuid * @param state the state * @param time the time * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addRefsetMembership(ComponentReference referencedComponent, UUID refexDynamicTypeUuid, State state, Long time) { return addAnnotation( referencedComponent, null, (DynamicSememeData[]) null, refexDynamicTypeUuid, state, time, null); } /** * Adds the relationship graph. * * @param concept the concept * @param logicalExpression the logical expression * @param stated the stated * @param time the time * @param module the module * @return the sememe chronology */ public SememeChronology<LogicGraphSememe<?>> addRelationshipGraph(ComponentReference concept, LogicalExpression logicalExpression, boolean stated, Long time, UUID module) { return this.addRelationshipGraph(concept, null, logicalExpression, stated, time, module); } /** * Adds the relationship graph. * * @param concept the concept * @param graphPrimordialUuid the graph primordial uuid * @param logicalExpression the logical expression * @param stated the stated * @param time the time * @param module the module * @return the sememe chronology */ public SememeChronology<LogicGraphSememe<?>> addRelationshipGraph(ComponentReference concept, UUID graphPrimordialUuid, LogicalExpression logicalExpression, boolean stated, Long time, UUID module) { final HashSet<UUID> temp = stated ? this.conceptHasStatedGraph : this.conceptHasInferredGraph; if (temp.contains(concept.getPrimordialUuid())) { throw new RuntimeException("Already have a " + (stated ? "stated" : "inferred") + " graph for concept " + concept.getPrimordialUuid()); } temp.add(concept.getPrimordialUuid()); @SuppressWarnings("rawtypes") final SememeBuilder sb = this.sememeBuilderService.getLogicalExpressionSememeBuilder( logicalExpression, concept.getNid(), stated ? this.conceptBuilderService.getDefaultLogicCoordinate() .getStatedAssemblageSequence() : this.conceptBuilderService.getDefaultLogicCoordinate() .getInferredAssemblageSequence()); // Build a LogicGraph UUID seed based on concept & logicExpression.getData(EXTERNAL) final StringBuilder byteString = new StringBuilder(); final byte[][] byteArray = logicalExpression.getData(DataTarget.EXTERNAL); for (byte[] byteArray1: byteArray) { byteString.append(Arrays.toString(byteArray1)); } // Create UUID from seed and assign SesemeBuilder the value final UUID generatedGraphPrimordialUuid = ConverterUUID.createNamespaceUUIDFromStrings( concept.getPrimordialUuid() .toString(), "" + stated, byteString.toString()); sb.setPrimordialUuid((graphPrimordialUuid != null) ? graphPrimordialUuid : generatedGraphPrimordialUuid); final ArrayList<OchreExternalizable> builtObjects = new ArrayList<>(); @SuppressWarnings("unchecked") final SememeChronology<LogicGraphSememe<?>> sci = (SememeChronology<LogicGraphSememe<?>>) sb.build( createStamp( State.ACTIVE, selectTime(time, concept), module), builtObjects); for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } this.ls.addGraph(); return sci; } /** * uses the concept time, UUID is created from the component UUID, the annotation value and type. * * @param referencedComponent the referenced component * @param annotationValue the annotation value * @param refsetUuid the refset uuid * @param state the state * @return the sememe chronology */ public SememeChronology<StringSememe<?>> addStaticStringAnnotation(ComponentReference referencedComponent, String annotationValue, UUID refsetUuid, State state) { @SuppressWarnings("rawtypes") final SememeBuilder sb = this.sememeBuilderService.getStringSememeBuilder( annotationValue, referencedComponent.getNid(), Get.identifierService() .getConceptSequenceForUuids(refsetUuid)); final StringBuilder temp = new StringBuilder(); temp.append(annotationValue); temp.append(refsetUuid.toString()); temp.append(referencedComponent.getPrimordialUuid() .toString()); sb.setPrimordialUuid(ConverterUUID.createNamespaceUUIDFromString(temp.toString())); final ArrayList<OchreExternalizable> builtObjects = new ArrayList<>(); @SuppressWarnings("unchecked") final SememeChronology<StringSememe<?>> sc = (SememeChronology<StringSememe<?>>) sb.build( createStamp(state, selectTime(null, referencedComponent)), builtObjects); for (final OchreExternalizable ochreObject: builtObjects) { this.writer.put(ochreObject); } this.ls.addAnnotation((referencedComponent.getTypeString() .length() > 0) ? referencedComponent.getTypeString() : getOriginStringForUuid( referencedComponent.getPrimordialUuid()), getOriginStringForUuid(refsetUuid)); return sc; } /** * uses the concept time, UUID is created from the component UUID, the annotation value and type. * * @param referencedComponent the referenced component * @param annotationValue the annotation value * @param refsetUuid the refset uuid * @param status the status * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addStringAnnotation(ComponentReference referencedComponent, String annotationValue, UUID refsetUuid, State status) { return addAnnotation( referencedComponent, null, new DynamicSememeData[] { new DynamicSememeStringImpl(annotationValue) }, refsetUuid, status, null, null); } /** * uses the concept time. * * @param referencedComponent the referenced component * @param uuidForCreatedAnnotation the uuid for created annotation * @param annotationValue the annotation value * @param refsetUuid the refset uuid * @param status the status * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addStringAnnotation(ComponentReference referencedComponent, UUID uuidForCreatedAnnotation, String annotationValue, UUID refsetUuid, State status) { return addAnnotation( referencedComponent, uuidForCreatedAnnotation, new DynamicSememeData[] { new DynamicSememeStringImpl(annotationValue) }, refsetUuid, status, null, null); } /** * Add an alternate ID to the concept. * * @param existingUUID the existing UUID * @param newUUID the new UUID */ public void addUUID(UUID existingUUID, UUID newUUID) { final ConceptChronologyImpl conceptChronology = (ConceptChronologyImpl) Get.conceptService() .getConcept(existingUUID); conceptChronology.addAdditionalUuids(newUUID); this.writer.put(conceptChronology); } /** * Generates the UUID, uses the component time. * * @param object the object * @param value the value * @param refsetUuid the refset uuid * @return the sememe chronology */ public SememeChronology<DynamicSememe<?>> addUUIDAnnotation(ComponentReference object, UUID value, UUID refsetUuid) { return addAnnotation( object, null, new DynamicSememeData[] { new DynamicSememeUUIDImpl(value) }, refsetUuid, null, null, null); } /** * Clear load stats. */ public void clearLoadStats() { this.ls = new LoadStats(); } /** * This method probably shouldn't be used - better to use the PropertyAssotion type. * * @param associationTypeConcept the association type concept * @param inverseName the inverse name * @deprecated - Better to set things up as {@link BPT_Associations} */ @Deprecated public void configureConceptAsAssociation(UUID associationTypeConcept, String inverseName) { final DynamicSememeColumnInfo[] colInfo = new DynamicSememeColumnInfo[] { new DynamicSememeColumnInfo( 0, DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMN_ASSOCIATION_TARGET_COMPONENT.getPrimordialUuid(), DynamicSememeDataType.UUID, null, true, true) }; configureConceptAsDynamicRefex( ComponentReference.fromConcept(associationTypeConcept), "Defines an Association Type", colInfo, null, null); addRefsetMembership( ComponentReference.fromConcept(associationTypeConcept), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME .getUUID(), State.ACTIVE, null); if (!StringUtils.isBlank(inverseName)) { final SememeChronology<DescriptionSememe<?>> inverseDesc = addDescription( ComponentReference.fromConcept( associationTypeConcept), inverseName, DescriptionType.SYNONYM, false, null, State.ACTIVE); addRefsetMembership( ComponentReference.fromChronology(inverseDesc), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getUUID(), State.ACTIVE, selectTime(null, ComponentReference.fromChronology(inverseDesc))); } BPT_Associations.registerAsAssociation(associationTypeConcept); } /** * Configure concept as dynamic refex. * * @param concept the concept * @param refexDescription the refex description * @param columns the columns * @param referencedComponentTypeRestriction the referenced component type restriction * @param referencedComponentTypeSubRestriction the referenced component type sub restriction */ public void configureConceptAsDynamicRefex(ComponentReference concept, String refexDescription, DynamicSememeColumnInfo[] columns, ObjectChronologyType referencedComponentTypeRestriction, SememeType referencedComponentTypeSubRestriction) { if (refexDescription == null) { throw new RuntimeException("Refex description is required"); } // See {@link DynamicSememeUsageDescription} class for more details on this format. // Add the special synonym to establish this as an assemblage concept // Need a custom UUID, otherwise duplicates are likely final UUID temp = ConverterUUID.createNamespaceUUIDFromStrings( concept.getPrimordialUuid() .toString(), refexDescription, DescriptionType.DEFINITION.name(), MetaData.US_ENGLISH_DIALECT.getPrimordialUuid() .toString(), MetaData.ENGLISH_LANGUAGE.getPrimordialUuid() .toString(), new Boolean("true").toString(), "DynamicSememeMarker"); final SememeChronology<DescriptionSememe<?>> desc = addDescription( concept, temp, refexDescription, DescriptionType.DEFINITION, true, null, State.ACTIVE); // Annotate the description as the 'special' type that means this concept is suitable for use as an assemblage concept addAnnotation( ComponentReference.fromChronology(desc), null, (DynamicSememeData) null, DynamicSememeConstants.get().DYNAMIC_SEMEME_DEFINITION_DESCRIPTION .getUUID(), State.ACTIVE, null); // define the data columns (if any) if ((columns != null) && (columns.length > 0)) { for (final DynamicSememeColumnInfo col: columns) { final DynamicSememeData[] data = LookupService.getService(DynamicSememeUtility.class) .configureDynamicSememeDefinitionDataForColumn(col); addAnnotation( concept, null, data, DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENSION_DEFINITION .getUUID(), State.ACTIVE, null, null); } final DynamicSememeArray<DynamicSememeData> indexInfo = LookupService.getService(DynamicSememeUtility.class) .configureColumnIndexInfo(columns); if (indexInfo != null) { addAnnotation( concept, null, new DynamicSememeData[] { indexInfo }, DynamicSememeConstants.get().DYNAMIC_SEMEME_INDEX_CONFIGURATION .getPrimordialUuid(), State.ACTIVE, null, null); } } registerDynamicSememeColumnInfo(concept.getPrimordialUuid(), columns); // Add the restriction information (if any) final DynamicSememeData[] data = LookupService.getService(DynamicSememeUtility.class) .configureDynamicSememeRestrictionData( referencedComponentTypeRestriction, referencedComponentTypeSubRestriction); if (data != null) { addAnnotation( concept, null, data, DynamicSememeConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION .getUUID(), State.ACTIVE, null, null); } } /** * Creates the concept. * * @param conceptPrimordialUuid the concept primordial uuid * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID conceptPrimordialUuid) { return createConcept(conceptPrimordialUuid, (Long) null, State.ACTIVE, null); } /** * Create a concept, automatically setting as many fields as possible (adds a description, calculates * the UUID, status current, etc). * * @param fsn the fsn * @param createSynonymFromFSN the create synonym from FSN * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(String fsn, boolean createSynonymFromFSN) { return createConcept(ConverterUUID.createNamespaceUUIDFromString(fsn), fsn, createSynonymFromFSN); } /** * Create a concept, link it to a parent via is_a, setting as many fields as possible automatically. * * @param fsn the fsn * @param createSynonymFromFSN the create synonym from FSN * @param parentConceptPrimordial the parent concept primordial * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(String fsn, boolean createSynonymFromFSN, UUID parentConceptPrimordial) { final ConceptChronology<? extends ConceptVersion<?>> concept = createConcept(fsn, createSynonymFromFSN); addParent(ComponentReference.fromConcept(concept), parentConceptPrimordial); return concept; } /** * Create a concept, automatically setting as many fields as possible (adds a description (en US) * status current, etc. * * @param conceptPrimordialUuid the concept primordial uuid * @param fsn the fsn * @param createSynonymFromFSN the create synonym from FSN * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID conceptPrimordialUuid, String fsn, boolean createSynonymFromFSN) { return createConcept(conceptPrimordialUuid, fsn, createSynonymFromFSN, null, State.ACTIVE); } /** * Just create a concept. * * @param conceptPrimordialUuid the concept primordial uuid * @param time - if null, set to default * @param status - if null, set to current * @param module - if null, uses the default * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID conceptPrimordialUuid, Long time, State status, UUID module) { final ConceptChronologyImpl conceptChronology = (ConceptChronologyImpl) Get.conceptService() .getConcept(conceptPrimordialUuid); conceptChronology.createMutableVersion(createStamp(status, time, module)); this.writer.put(conceptChronology); this.ls.addConcept(); return conceptChronology; } /** * Create a concept, link it to a parent via is_a, setting as many fields as possible automatically. * * @param conceptPrimordialUuid the concept primordial uuid * @param fsn the fsn * @param createSynonymFromFSN the create synonym from FSN * @param relParentPrimordial the rel parent primordial * @return the concept chronology<? extends concept version<?>> */ public final ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID conceptPrimordialUuid, String fsn, boolean createSynonymFromFSN, UUID relParentPrimordial) { final ConceptChronology<? extends ConceptVersion<?>> concept = createConcept( conceptPrimordialUuid, fsn, createSynonymFromFSN); addParent(ComponentReference.fromConcept(concept), relParentPrimordial); return concept; } /** * Create a concept, automatically setting as many fields as possible (adds a description (en US)). * * @param conceptPrimordialUuid the concept primordial uuid * @param fsn the fsn * @param createSynonymFromFSN the create synonym from FSN * @param time - set to now if null * @param status the status * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID conceptPrimordialUuid, String fsn, boolean createSynonymFromFSN, Long time, State status) { final ConceptChronology<? extends ConceptVersion<?>> cc = createConcept( conceptPrimordialUuid, time, status, null); final ComponentReference concept = ComponentReference.fromConcept(cc); addFullySpecifiedName(concept, fsn); if (createSynonymFromFSN) { addDescription( concept, fsn.endsWith(METADATA_SEMANTIC_TAG) ? fsn.substring(0, fsn.lastIndexOf(METADATA_SEMANTIC_TAG)) : fsn, DescriptionType.SYNONYM, true, null, State.ACTIVE); } return cc; } /** * Utility method to build and store a concept. * * @param primordial - optional * @param fsnName the fsn name * @param preferredName - optional * @param altName - optional * @param definition - optional * @param relParentPrimordial the rel parent primordial * @param secondParent - optional * @return the concept chronology<? extends concept version<?>> */ public ConceptChronology<? extends ConceptVersion<?>> createConcept(UUID primordial, String fsnName, String preferredName, String altName, String definition, UUID relParentPrimordial, UUID secondParent) { final ConceptChronology<? extends ConceptVersion<?>> concept = createConcept( (primordial == null) ? ConverterUUID.createNamespaceUUIDFromString( fsnName) : primordial, fsnName, StringUtils.isEmpty(preferredName) ? true : false); final LogicalExpressionBuilder leb = this.expressionBuilderService.getLogicalExpressionBuilder(); if (secondParent == null) { NecessarySet( And(ConceptAssertion(Get.identifierService() .getConceptSequenceForUuids(relParentPrimordial), leb))); } else { NecessarySet( And( ConceptAssertion(Get.identifierService() .getConceptSequenceForUuids(relParentPrimordial), leb), ConceptAssertion(Get.identifierService() .getConceptSequenceForUuids(secondParent), leb))); } final LogicalExpression logicalExpression = leb.build(); addRelationshipGraph(ComponentReference.fromConcept(concept), null, logicalExpression, true, null, null); if (StringUtils.isNotEmpty(preferredName)) { addDescription( ComponentReference.fromConcept(concept), preferredName, DescriptionType.SYNONYM, true, null, State.ACTIVE); } if (StringUtils.isNotEmpty(altName)) { addDescription( ComponentReference.fromConcept(concept), altName, DescriptionType.SYNONYM, false, null, State.ACTIVE); } if (StringUtils.isNotEmpty(definition)) { addDescription( ComponentReference.fromConcept(concept), definition, DescriptionType.DEFINITION, true, null, State.ACTIVE); } return concept; } /** * Creates column concepts (for the column labels) for each provided columnName, then creates a property with a multi-column data set * each column being of type string, and optional. * * @param sememeName the sememe name * @param columnNames - Create concepts to represent column names for each item here. Supports a stupid hack, where if the * first two characters of a string in this array are '[]' - it will create a dynamic sememe array type for strings, rather than a single string. * @param columnTypes - optional - if not provided, makes all columns strings. If provided, must match size of columnNames * @return the property */ public Property createMultiColumnDynamicStringSememe(String sememeName, String[] columnNames, DynamicSememeDataType[] columnTypes) { final DynamicSememeColumnInfo[] cols = new DynamicSememeColumnInfo[columnNames.length]; for (int i = 0; i < cols.length; i++) { String colName; DynamicSememeDataType type; if (columnNames[i].startsWith("[]")) { colName = columnNames[i].substring(2, columnNames[i].length()); type = DynamicSememeDataType.ARRAY; } else { colName = columnNames[i]; type = (columnTypes == null) ? DynamicSememeDataType.STRING : columnTypes[i]; } final UUID descriptionConcept = createConcept( colName, true, DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMNS .getPrimordialUuid()).getPrimordialUuid(); cols[i] = new DynamicSememeColumnInfo(i, descriptionConcept, type, null, false, true); } return new Property(null, sememeName, null, null, false, Integer.MAX_VALUE, cols); } /** * Set up all the boilerplate stuff. * * @param state - state or null (for current) * @param time - time or null (for default) * @return the int */ public int createStamp(State state, Long time) { return createStamp(state, time, null); } /** * Set up all the boilerplate stuff. * * @param state - state or null (for active) * @param time - time or null (for default) * @param module the module * @return the int */ public int createStamp(State state, Long time, UUID module) { return Get.stampService() .getStampSequence( (state == null) ? State.ACTIVE : state, (time == null) ? this.defaultTime : time, this.authorSeq, ((module == null) ? this.module.getSequence() : Get.identifierService() .getConceptSequenceForUuids(module)), this.terminologyPathSeq); } /** * Create metadata concepts from the PropertyType structure. * * @param propertyTypes the property types * @param parentPrimordial the parent primordial * @throws Exception the exception */ public void loadMetaDataItems(Collection<PropertyType> propertyTypes, UUID parentPrimordial) throws Exception { for (final PropertyType pt: propertyTypes) { if (pt instanceof BPT_Skip) { continue; } createConcept( pt.getPropertyTypeUUID(), pt.getPropertyTypeDescription() + METADATA_SEMANTIC_TAG, true, parentPrimordial); UUID secondParent = null; if (pt instanceof BPT_Refsets) { secondParent = setupWbPropertyMetadata( MetaData.SOLOR_REFSETS.getPrimordialUuid(), (BPT_DualParentPropertyType) pt); } else if (pt instanceof BPT_Descriptions) { // should only do this once, in case we see a BPT_Descriptions more than once secondParent = setupWbPropertyMetadata( MetaData.DESCRIPTION_TYPE_IN_SOURCE_TERMINOLOGY.getPrimordialUuid(), (BPT_DualParentPropertyType) pt); } else if (pt instanceof BPT_Relations) { // should only do this once, in case we see a BPT_Relations more than once secondParent = setupWbPropertyMetadata( MetaData.RELATIONSHIP_TYPE_IN_SOURCE_TERMINOLOGY.getPrimordialUuid(), (BPT_DualParentPropertyType) pt); } for (final Property p: pt.getProperties()) { if (p.isFromConceptSpec()) { // This came from a conceptSpecification (metadata in ISAAC), and we don't need to create it. // Just need to add one relationship to the existing concept. addParent(ComponentReference.fromConcept(p.getUUID()), pt.getPropertyTypeUUID()); } else { // don't feed in the 'definition' if it is an association, because that will be done by the configureConceptAsDynamicRefex method final ConceptChronology<? extends ConceptVersion<?>> concept = createConcept( p.getUUID(), p.getSourcePropertyNameFSN() + METADATA_SEMANTIC_TAG, p.getSourcePropertyNameFSN(), p.getSourcePropertyAltName(), ((p instanceof PropertyAssociation) ? null : p.getSourcePropertyDefinition()), pt.getPropertyTypeUUID(), secondParent); if (pt.createAsDynamicRefex()) { configureConceptAsDynamicRefex( ComponentReference.fromConcept(concept), findFirstNotEmptyString( p.getSourcePropertyDefinition(), p.getSourcePropertyAltName(), p.getSourcePropertyNameFSN()), p.getDataColumnsForDynamicRefex(), null, null); } else if (p instanceof PropertyAssociation) { // TODO need to migrate code from api-util (AssociationType, etc) down into the ISAAC packages... integrate here, at least at doc level // associations return false for "createAsDynamicRefex" final PropertyAssociation item = (PropertyAssociation) p; // Make this a dynamic refex - with the association column info configureConceptAsDynamicRefex( ComponentReference.fromConcept(concept), item.getSourcePropertyDefinition(), item.getDataColumnsForDynamicRefex(), item.getAssociationComponentTypeRestriction(), item.getAssociationComponentTypeSubRestriction()); // Add this concept to the association sememe addRefsetMembership( ComponentReference.fromConcept(concept), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME .getUUID(), State.ACTIVE, null); // add the inverse name, if it has one if (!StringUtils.isBlank(item.getAssociationInverseName())) { final SememeChronology<DescriptionSememe<?>> inverseDesc = addDescription( ComponentReference.fromConcept( concept), item.getAssociationInverseName(), DescriptionType.SYNONYM, false, null, State.ACTIVE); addRefsetMembership( ComponentReference.fromChronology(inverseDesc), DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME .getUUID(), State.ACTIVE, selectTime(null, ComponentReference.fromChronology(inverseDesc))); } } } } } } /** * Create metadata TtkConceptChronicles from the PropertyType structure * NOTE - Refset types are not stored!. * * @param propertyType the property type * @param parentPrimordial the parent primordial * @throws Exception the exception */ public void loadMetaDataItems(PropertyType propertyType, UUID parentPrimordial) throws Exception { final ArrayList<PropertyType> propertyTypes = new ArrayList<>(); propertyTypes.add(propertyType); loadMetaDataItems(propertyTypes, parentPrimordial); } /** * Load terminology metadata attributes. * * @param terminologyMetadataRootConcept the terminology metadata root concept * @param converterSourceArtifactVersion the converter source artifact version * @param converterSourceReleaseDate the converter source release date * @param converterOutputArtifactVersion the converter output artifact version * @param converterOutputArtifactClassifier the converter output artifact classifier * @param converterVersion the converter version */ public void loadTerminologyMetadataAttributes(ComponentReference terminologyMetadataRootConcept, String converterSourceArtifactVersion, Optional<String> converterSourceReleaseDate, String converterOutputArtifactVersion, Optional<String> converterOutputArtifactClassifier, String converterVersion) { addStaticStringAnnotation( terminologyMetadataRootConcept, converterSourceArtifactVersion, MetaData.SOURCE_ARTIFACT_VERSION.getPrimordialUuid(), State.ACTIVE); addStaticStringAnnotation( terminologyMetadataRootConcept, converterOutputArtifactVersion, MetaData.CONVERTED_IBDF_ARTIFACT_VERSION.getPrimordialUuid(), State.ACTIVE); addStaticStringAnnotation( terminologyMetadataRootConcept, converterVersion, MetaData.CONVERTER_VERSION.getPrimordialUuid(), State.ACTIVE); if (converterOutputArtifactClassifier.isPresent() && StringUtils.isNotBlank(converterOutputArtifactClassifier.get())) { addStaticStringAnnotation( terminologyMetadataRootConcept, converterOutputArtifactClassifier.get(), MetaData.CONVERTED_IBDF_ARTIFACT_CLASSIFIER.getPrimordialUuid(), State.ACTIVE); } if (converterSourceReleaseDate.isPresent() && StringUtils.isNotBlank(converterSourceReleaseDate.get())) { addStaticStringAnnotation( terminologyMetadataRootConcept, converterSourceReleaseDate.get(), MetaData.SOURCE_RELEASE_DATE.getPrimordialUuid(), State.ACTIVE); } } /** * Register dynamic sememe column info. * * @param sememeUUID the sememe UUID * @param columnInfo the column info */ public final void registerDynamicSememeColumnInfo(UUID sememeUUID, DynamicSememeColumnInfo[] columnInfo) { this.refexAllowedColumnTypes.put(sememeUUID, columnInfo); } /** * uses providedTime first, if present, followed by readTimeFrom. * * Note, this still may return null. * * @param providedTime the provided time * @param readTimeFrom the read time from * @return the long */ public Long selectTime(Long providedTime, ComponentReference readTimeFrom) { if (providedTime != null) { return providedTime; } else { return readTimeFrom.getTime(); } } /** * Shutdown. * * @throws IOException Signals that an I/O exception has occurred. */ public void shutdown() throws IOException { this.writer.close(); LookupService.shutdownSystem(); ConverterUUID.clearCache(); clearLoadStats(); } /** * Find first not empty string. * * @param strings the strings * @return the string */ private String findFirstNotEmptyString(String... strings) { for (final String s: strings) { if (StringUtils.isNotEmpty(s)) { return s; } } return ""; } /** * Setup wb property metadata. * * @param refsetValueParent the refset value parent * @param pt the pt * @return the uuid * @throws Exception the exception */ private UUID setupWbPropertyMetadata(UUID refsetValueParent, BPT_DualParentPropertyType pt) throws Exception { if (pt.getSecondParentName() == null) { throw new RuntimeException("Unhandled case!"); } // Create the terminology specific refset type as a child - this is just an organization concept // under description type in source terminology or relationship type in source terminology final UUID temp = createConcept( ConverterUUID.createNamespaceUUIDFromString(pt.getSecondParentName(), true), pt.getSecondParentName() + METADATA_SEMANTIC_TAG, true, refsetValueParent).getPrimordialUuid(); pt.setSecondParentId(temp); return temp; } /** * Validate data types. * * @param refexDynamicTypeUuid the refex dynamic type uuid * @param values the values */ private void validateDataTypes(UUID refexDynamicTypeUuid, DynamicSememeData[] values) { // TODO this should be a much better validator - checking all of the various things in RefexDynamicCAB.validateData - or in // generateMetadataEConcepts - need to enforce the restrictions defined in the columns in the validators if (!this.refexAllowedColumnTypes.containsKey(refexDynamicTypeUuid)) { throw new RuntimeException("Attempted to store data on a concept not configured as a dynamic sememe"); } final DynamicSememeColumnInfo[] colInfo = this.refexAllowedColumnTypes.get(refexDynamicTypeUuid); if ((values != null) && (values.length > 0)) { if (colInfo != null) { for (int i = 0; i < values.length; i++) { DynamicSememeColumnInfo column = null; for (final DynamicSememeColumnInfo x: colInfo) { if (x.getColumnOrder() == i) { column = x; break; } } if (column == null) { throw new RuntimeException("Column count mismatch"); } else { if ((values[i] == null) && column.isColumnRequired()) { throw new RuntimeException("Missing column data for column " + column.getColumnName()); } else if ((values[i] != null) && (column.getColumnDataType() != values[i].getDynamicSememeDataType()) && (column.getColumnDataType() != DynamicSememeDataType.POLYMORPHIC)) { throw new RuntimeException( "Datatype mismatch - " + column.getColumnDataType() + " - " + values[i].getDynamicSememeDataType()); } } } } else if (values.length > 0) { throw new RuntimeException("Column count mismatch - this dynamic sememe doesn't allow columns!"); } } else if (colInfo != null) { for (final DynamicSememeColumnInfo ci: colInfo) { if (ci.isColumnRequired()) { throw new RuntimeException("Missing column data for column " + ci.getColumnName()); } } } } //~--- get methods --------------------------------------------------------- /** * Checks if configured as dynamic sememe. * * @param refexDynamicTypeUuid the refex dynamic type uuid * @return true, if configured as dynamic sememe */ private boolean isConfiguredAsDynamicSememe(UUID refexDynamicTypeUuid) { return this.refexAllowedColumnTypes.containsKey(refexDynamicTypeUuid); } /** * Gets the load stats. * * @return the load stats */ public LoadStats getLoadStats() { return this.ls; } /** * Gets the module. * * @return the module */ public ComponentReference getModule() { return this.module; } /** * Gets the origin string for uuid. * * @param uuid the uuid * @return the origin string for uuid */ private String getOriginStringForUuid(UUID uuid) { final String temp = ConverterUUID.getUUIDCreationString(uuid); if (temp != null) { final String[] parts = temp.split(":"); if ((parts != null) && (parts.length > 1)) { return parts[parts.length - 1]; } return temp; } return "Unknown"; } }