/*
* 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.provider.query.associations;
//~--- JDK imports ------------------------------------------------------------
import java.util.Optional;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import sh.isaac.api.Get;
import sh.isaac.api.LookupService;
import sh.isaac.api.State;
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.dynamicSememe.DynamicSememeColumnInfo;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeDataType;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription;
import sh.isaac.api.constants.DynamicSememeConstants;
import sh.isaac.api.coordinate.EditCoordinate;
import sh.isaac.api.coordinate.LanguageCoordinate;
import sh.isaac.api.coordinate.StampCoordinate;
import sh.isaac.MetaData;
import sh.isaac.model.configuration.LanguageCoordinates;
import sh.isaac.provider.query.lucene.indexers.SememeIndexerConfiguration;
import sh.isaac.utility.Frills;
//~--- classes ----------------------------------------------------------------
/**
* The Class AssociationType.
*/
public class AssociationType {
/** The Constant log. */
private static final Logger log = LogManager.getLogger();
//~--- fields --------------------------------------------------------------
/** The association sequence. */
private final int associationSequence;
/** The association name. */
private String associationName;
/** The association inverse name. */
private Optional<String> associationInverseName;
/** The description. */
private String description;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new association type.
*
* @param conceptNidOrSequence the concept nid or sequence
*/
private AssociationType(int conceptNidOrSequence) {
this.associationSequence = Get.identifierService()
.getConceptSequence(conceptNidOrSequence);
}
//~--- methods -------------------------------------------------------------
/**
* Create and store a new mapping set in the DB.
* @param associationName - The name of the association (used for the FSN and preferred term of the underlying concept)
* @param associationInverseName - (optional) inverse name of the association (if it makes sense for the association)
* @param description - (optional) description that describes the purpose of the association
* @param referencedComponentRestriction - (optional) - may be null - if provided - this restricts the type of object referenced by the nid or
* UUID that is set for the referenced component in an instance of this sememe. If {@link ObjectChronologyType#UNKNOWN_NID} is passed, it is ignored, as
* if it were null.
* @param referencedComponentSubRestriction - (optional) - may be null - subtype restriction for {@link ObjectChronologyType#SEMEME} restrictions
* @param stampCoord - optional - used during the readback to create the return object. See {@link #read(int, StampCoordinate)}
* @param editCoord - optional - the edit coordinate to use when creating the association. Uses the system default if not provided.
* @return the concept sequence of the created concept that carries the association definition
*/
@SuppressWarnings("deprecation")
public static AssociationType createAssociation(String associationName,
String associationInverseName,
String description,
ObjectChronologyType referencedComponentRestriction,
SememeType referencedComponentSubRestriction,
StampCoordinate stampCoord,
EditCoordinate editCoord) {
try {
final EditCoordinate localEditCoord = ((editCoord == null) ? Get.configurationService()
.getDefaultEditCoordinate()
: editCoord);
// We need to create a new concept - which itself is defining a dynamic sememe - so set that up here.
final DynamicSememeUsageDescription rdud =
Frills.createNewDynamicSememeUsageDescriptionConcept(associationName,
associationName,
StringUtils.isBlank(description)
? "Defines the association type " +
associationInverseName
: description,
new DynamicSememeColumnInfo[] {
new DynamicSememeColumnInfo(0,
DynamicSememeConstants.get().DYNAMIC_SEMEME_COLUMN_ASSOCIATION_TARGET_COMPONENT.getUUID(),
DynamicSememeDataType.UUID,
null,
false,
true) },
DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME
.getNid(),
referencedComponentRestriction,
referencedComponentSubRestriction,
editCoord);
Get.workExecutors().getExecutor().execute(() -> {
try {
SememeIndexerConfiguration.configureColumnsToIndex(
rdud.getDynamicSememeUsageDescriptorSequence(),
new Integer[] { 0 },
true);
} catch (final Exception e) {
log.error("Unexpected error enabling the index on newly created association!", e);
}
});
// Then add the inverse name, if present.
if (!StringUtils.isBlank(associationInverseName)) {
final ObjectChronology<?> builtDesc = LookupService.get()
.getService(DescriptionBuilderService.class)
.getDescriptionBuilder(associationInverseName,
rdud.getDynamicSememeUsageDescriptorSequence(),
MetaData.SYNONYM,
MetaData.ENGLISH_LANGUAGE)
.build(localEditCoord, ChangeCheckerMode.ACTIVE)
.getNoThrow();
Get.sememeBuilderService()
.getDynamicSememeBuilder(builtDesc.getNid(),
DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME
.getSequence())
.build(localEditCoord, ChangeCheckerMode.ACTIVE)
.getNoThrow();
Get.commitService()
.commit("add description to association")
.get();
}
// Add the association marker sememe
Get.sememeBuilderService()
.getDynamicSememeBuilder(Get.identifierService()
.getConceptNid(rdud.getDynamicSememeUsageDescriptorSequence()),
DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_SEMEME
.getSequence())
.build(localEditCoord, ChangeCheckerMode.ACTIVE)
.getNoThrow();
Get.commitService()
.commit("mark assocation as association type sememe")
.get();
// final get is to wait for commit completion
return read(rdud.getDynamicSememeUsageDescriptorSequence(),
stampCoord,
LanguageCoordinates.getUsEnglishLanguagePreferredTermCoordinate());
} catch (final Exception e) {
log.error("Unexpected error creating association", e);
throw new RuntimeException(e);
}
}
/**
* Read all details that define an Association.
*
* @param conceptNidOrSequence The concept that represents the association
* @param stamp optional - uses system default if not provided.
* @param language optional - uses system default if not provided
* @return the association type
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static AssociationType read(int conceptNidOrSequence, StampCoordinate stamp, LanguageCoordinate language) {
final AssociationType at = new AssociationType(conceptNidOrSequence);
final int conceptNid = Get.identifierService()
.getConceptNid(at.getAssociationTypeSequenece());
final StampCoordinate localStamp = ((stamp == null) ? Get.configurationService()
.getDefaultStampCoordinate()
: stamp);
final LanguageCoordinate localLanguage = ((language == null) ? Get.configurationService()
.getDefaultLanguageCoordinate()
: language);
at.associationName = Get.conceptService()
.getSnapshot(localStamp, localLanguage)
.conceptDescriptionText(conceptNid);
// Find the inverse name
for (final DescriptionSememe<?> desc: Frills.getDescriptionsOfType(conceptNid,
MetaData.SYNONYM,
localStamp.makeAnalog(State.ACTIVE))) {
if (Get.sememeService()
.getSememesForComponentFromAssemblage(desc.getNid(),
DynamicSememeConstants.get().DYNAMIC_SEMEME_ASSOCIATION_INVERSE_NAME
.getSequence())
.anyMatch(nestedSememe -> {
if (nestedSememe.getSememeType() == SememeType.DYNAMIC) {
return ((SememeChronology) nestedSememe).getLatestVersion(DynamicSememe.class,
localStamp)
.isPresent();
}
return false;
})) {
at.associationInverseName = Optional.of(desc.getText());
}
}
// find the description
for (final DescriptionSememe<?> desc: Frills.getDescriptionsOfType(Get.identifierService()
.getConceptNid(at.getAssociationTypeSequenece()),
MetaData.DEFINITION_DESCRIPTION_TYPE,
localStamp.makeAnalog(State.ACTIVE))) {
if (Frills.isDescriptionPreferred(desc.getNid(), localStamp) &&Get.sememeService().getSememesForComponentFromAssemblage(desc.getNid(),
DynamicSememeConstants.get().DYNAMIC_SEMEME_DEFINITION_DESCRIPTION.getSequence()).anyMatch(
nestedSememe -> {
if (nestedSememe.getSememeType() == SememeType.DYNAMIC) {
return ((SememeChronology) nestedSememe).getLatestVersion(DynamicSememe.class, localStamp)
.isPresent();
}
return false;
})) {
at.description = desc.getText();
}
}
if (at.associationInverseName == null) {
at.associationInverseName = Optional.empty();
}
if (at.description == null) {
at.description = "-No description on path!-";
}
return at;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the association inverse name.
*
* @return the inverse name of the association (if present) (Read from the association type concept)
*/
public Optional<String> getAssociationInverseName() {
return this.associationInverseName;
}
/**
* Gets the association name.
*
* @return the association name
*/
public String getAssociationName() {
return this.associationName;
}
/**
* Gets the association type concept.
*
* @return the association type concept
*/
public ConceptChronology<? extends ConceptVersion<?>> getAssociationTypeConcept() {
return Get.conceptService()
.getConcept(this.associationSequence);
}
/**
* Gets the association type sequenece.
*
* @return the concept sequence of the association type concept
*/
public int getAssociationTypeSequenece() {
return this.associationSequence;
}
/**
* Gets the description.
*
* @return the description
*/
public String getDescription() {
return this.description;
}
}