Java tutorial
/* * Copyright 2011-2018 B2i Healthcare Pte Ltd, http://b2i.sg * * 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. */ package com.b2international.snowowl.snomed.datastore; import static com.b2international.snowowl.datastore.cdo.CDOUtils.getObjectIfExists; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.ENTIRE_TERM_CASE_INSENSITIVE; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.EXISTENTIAL_RESTRICTION_MODIFIER; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.FULLY_SPECIFIED_NAME; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.INFERRED_RELATIONSHIP; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.IS_A; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.PRIMITIVE; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.REFSET_DESCRIPTION_TYPE; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.STATED_RELATIONSHIP; import static com.b2international.snowowl.snomed.SnomedConstants.Concepts.SYNONYM; import static com.b2international.snowowl.snomed.datastore.SnomedDeletionPlanMessages.COMPONENT_IS_RELEASED_MESSAGE; import static com.b2international.snowowl.snomed.datastore.SnomedDeletionPlanMessages.UNABLE_TO_DELETE_CONCEPT_DUE_TO_RELEASED_INBOUND_RSHIP_MESSAGE; import static com.b2international.snowowl.snomed.datastore.SnomedDeletionPlanMessages.UNABLE_TO_DELETE_DESCRIPTION_TYPE_CONCEPT_MESSAGE; import static com.b2international.snowowl.snomed.datastore.SnomedDeletionPlanMessages.UNABLE_TO_DELETE_REFERENCE_SET_MESSAGE; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Collections.emptyList; import static org.eclipse.emf.cdo.common.id.CDOID.NULL; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import com.b2international.commons.options.OptionsBuilder; import com.b2international.index.revision.Revision; import com.b2international.snowowl.core.ApplicationContext; import com.b2international.snowowl.core.api.IBranchPath; import com.b2international.snowowl.core.api.ILookupService; import com.b2international.snowowl.core.api.SnowowlServiceException; import com.b2international.snowowl.core.domain.BranchContext; import com.b2international.snowowl.core.domain.IComponent; import com.b2international.snowowl.core.events.bulk.BulkRequest; import com.b2international.snowowl.core.events.bulk.BulkResponse; import com.b2international.snowowl.core.exceptions.ComponentNotFoundException; import com.b2international.snowowl.core.exceptions.ConflictException; import com.b2international.snowowl.core.terminology.ComponentCategory; import com.b2international.snowowl.datastore.CDOEditingContext; import com.b2international.snowowl.datastore.request.RepositoryRequests; import com.b2international.snowowl.eventbus.IEventBus; import com.b2international.snowowl.snomed.Component; import com.b2international.snowowl.snomed.Concept; import com.b2international.snowowl.snomed.Concepts; import com.b2international.snowowl.snomed.Description; import com.b2international.snowowl.snomed.Inactivatable; import com.b2international.snowowl.snomed.Relationship; import com.b2international.snowowl.snomed.SnomedFactory; import com.b2international.snowowl.snomed.core.domain.SnomedRelationship; import com.b2international.snowowl.snomed.core.domain.refset.SnomedReferenceSetMember; import com.b2international.snowowl.snomed.core.domain.refset.SnomedReferenceSetMembers; import com.b2international.snowowl.snomed.core.preference.ModulePreference; import com.b2international.snowowl.snomed.core.store.SnomedComponents; import com.b2international.snowowl.snomed.datastore.id.ISnomedIdentifierService; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedConceptDocument; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedDocument; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedRefSetMemberIndexEntry.Fields; import com.b2international.snowowl.snomed.datastore.request.SnomedRefSetMemberSearchRequestBuilder; import com.b2international.snowowl.snomed.datastore.request.SnomedRequests; import com.b2international.snowowl.snomed.mrcm.AttributeConstraint; import com.b2international.snowowl.snomed.mrcm.ConceptModel; import com.b2international.snowowl.snomed.mrcm.ConstraintBase; import com.b2international.snowowl.snomed.snomedrefset.SnomedMappingRefSet; import com.b2international.snowowl.snomed.snomedrefset.SnomedRefSet; import com.b2international.snowowl.snomed.snomedrefset.SnomedRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedRegularRefSet; import com.b2international.snowowl.snomed.snomedrefset.SnomedStructuralRefSet; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; /** * SNOMED CT RF2 specific editing context subclass of {@link CDOEditingContext} * providing retrieval, builder and other utility methods. * */ public class SnomedEditingContext extends BaseSnomedEditingContext { private SnomedRefSetEditingContext refSetEditingContext; private Concept moduleConcept; private String nameSpace; private boolean uniquenessCheckEnabled = true; private Set<String> newComponentIds = Collections.synchronizedSet(Sets.<String>newHashSet()); private SnomedDeletionPlan deletionPlan = new SnomedDeletionPlan(); /** * Creates a new SNOMED CT core components editing context on the specified branch of the SNOMED CT repository. * * @param branchPath the branch path to use */ public SnomedEditingContext(IBranchPath branchPath) { this(branchPath, getDefaultNamespace()); } /** * Creates a new SNOMED CT core components editing context on the specified * branch of the SNOMED CT repository with the specified namespace to use * when generating new componentIds. * * @param branchPath * @param nameSpace */ public SnomedEditingContext(IBranchPath branchPath, String nameSpace) { super(branchPath); init(nameSpace); } private void init(final String namespace) { this.refSetEditingContext = new SnomedRefSetEditingContext(this); setNamespace(namespace); } private void setNamespace(String nameSpace) { this.nameSpace = checkNotNull(nameSpace, "No namespace configured"); } @Override public CDOCommitInfo commit(String commitMessage, IProgressMonitor monitor) throws SnowowlServiceException { try { return super.commit(commitMessage, monitor); } catch (Exception e) { // releaseIds(); throw e; } } @Override public void clearCache() { super.clearCache(); getRefSetEditingContext().clearCache(); } @Override protected <T extends CDOObject> Iterable<? extends IComponent> fetchComponents(Collection<String> componentIds, Class<T> type) { if (type.isAssignableFrom(Concept.class)) { return SnomedRequests.prepareSearchConcept().all().filterByIds(componentIds) .setFields(SnomedDocument.Fields.ID, Revision.STORAGE_KEY) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } else if (type.isAssignableFrom(Description.class)) { return SnomedRequests.prepareSearchDescription().all().filterByIds(componentIds) .setFields(SnomedDocument.Fields.ID, Revision.STORAGE_KEY) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } else if (type.isAssignableFrom(Relationship.class)) { return SnomedRequests.prepareSearchRelationship().all().filterByIds(componentIds) .setFields(SnomedDocument.Fields.ID, Revision.STORAGE_KEY) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } else if (type.isAssignableFrom(SnomedRefSetMember.class)) { return SnomedRequests.prepareSearchMember().all().filterByIds(componentIds) .setFields(SnomedDocument.Fields.ID, Revision.STORAGE_KEY) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } else if (type.isAssignableFrom(SnomedRefSet.class)) { return SnomedRequests.prepareSearchRefSet().all().filterByIds(componentIds) .setFields(SnomedDocument.Fields.ID, SnomedConceptDocument.Fields.REFSET_STORAGEKEY) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } throw new UnsupportedOperationException("Cannot get storage keys for " + type); } public void releaseIds() { if (!newComponentIds.isEmpty()) { final IEventBus bus = ApplicationContext.getInstance().getServiceChecked(IEventBus.class); SnomedRequests.identifiers().prepareRelease().setComponentIds(newComponentIds) .build(SnomedDatastoreActivator.REPOSITORY_UUID).execute(bus).getSync(); newComponentIds.clear(); } } /** * Unlike {@link CDOEditingContext#getContents()} this method returns with * the {@link Concepts#getConcepts() concepts container} of the default * {@link Concepts} instance stored in the root CDO resource. * <br>May return with {@code null} if does not exist yet. * <p> * {@inheritDoc} * @return the root container of all concepts. */ @Override @SuppressWarnings("unchecked") public List<Concept> getContents() { final CDOID cdoid = SnomedConceptsContainerCdoIdSupplier.INSTANCE.getDefaultContainerCdoId(); if (NULL.equals(cdoid)) { return emptyList(); } return ((Concepts) getObjectIfExists(transaction, cdoid)).getConcepts(); } @Override public void add(EObject object) { if (object instanceof AttributeConstraint) { getConstraints().add((AttributeConstraint) object); } else { super.add(object); } } public List<ConstraintBase> getConstraints() { // XXX: The concept model root object should always exist at this point, thanks to the repository initializer final CDOResource mrcmResource = transaction .getOrCreateResource(SnomedDatastoreActivator.MRCM_ROOT_RESOURCE_NAME); final ConceptModel conceptModel = (ConceptModel) mrcmResource.getContents().get(0); return conceptModel.getConstraints(); } public SnomedRefSetEditingContext getRefSetEditingContext() { return refSetEditingContext; } ///////////////////////////////////////////////////////////////////////// // Terminology component retrieval operations ///////////////////////////////////////////////////////////////////////// /** * Returns a SNOMED CT Concept database object from the store by loading it, or from the transaction if previously added via {@link #add(EObject)} * or {@link #addAll(Collection)}, otherwise if not found throws a {@link ComponentNotFoundException}. * * @param conceptId * - the SNOMED CT identifier of the concept * @return the concept, never <code>null</code> * @throws ComponentNotFoundException * if the concept could not be retrieved */ public final Concept getConcept(String conceptId) { return lookup(conceptId, Concept.class); } /** * @deprecated - use and see {@link SnomedEditingContext#getConcept(String)}, will be removed in 4.6 */ public Concept findConceptById(final String conceptId) { return getConcept(conceptId); } /** * Builds a new default SNOMED CT concept with a fully specified name description and ISA relationship to the specified parent concept. * @param fullySpecifiedName * @param parentConceptId * @return * @deprecated - will be removed in 4.4 */ public Concept buildDefaultConcept(String conceptId, String fullySpecifiedName, String parentConceptId) { return buildDefaultConcept(conceptId, fullySpecifiedName, findConceptById(parentConceptId)); } /** * @param fullySpecifiedName * @param parentConcept can not be <tt>null</tt>. * @return a valid concept populated with default values and the specified properties * @deprecated - will be replaced with new component builder API in 4.4 */ public Concept buildDefaultConcept(String conceptId, String fullySpecifiedName, Concept parentConcept) { checkNotNull(parentConcept, "parentConcept"); Concept concept = SnomedFactory.eINSTANCE.createConcept(); add(concept); // set concept properties concept.setId(conceptId); concept.setActive(true); concept.setDefinitionStatus(findConceptById(PRIMITIVE)); concept.setModule(getDefaultModuleConcept()); // add FSN Description description = buildDefaultDescription(fullySpecifiedName, FULLY_SPECIFIED_NAME); description.setConcept(concept); // add 'Is a' relationships to parent if specified buildDefaultIsARelationship(parentConcept, concept, STATED_RELATIONSHIP); buildDefaultIsARelationship(parentConcept, concept, INFERRED_RELATIONSHIP); return concept; } private void buildDefaultIsARelationship(Concept parentConcept, Concept concept, String charTypeId) { Relationship relationship = buildDefaultRelationship(concept, findConceptById(IS_A), parentConcept, findConceptById(charTypeId)); relationship.setModule(concept.getModule()); } /**Returns with the currently used language type reference set, falls back to an existing language if the configured identifier can not be resolved.*/ public SnomedStructuralRefSet getLanguageRefSet(String languageReferenceSetId) { return lookup(languageReferenceSetId, SnomedStructuralRefSet.class); } /** * @param source can be <tt>null</tt> then source will be an empty concept with empty <tt>String</tt> description. * @param type can be <tt>null</tt> then type will be an empty concept with empty <tt>String</tt> description. * @param destination can be <tt>null</tt> then destination will be an empty concept with empty <tt>String</tt> description. * @param characteristicType can be <tt>null</tt> then characteristicType will be Defining concept. * @param effectiveTime * @return a valid relationship populated with default values and the specified properties * @deprecated - use {@link SnomedComponents#newRelationship()} instead */ public Relationship buildDefaultRelationship(Concept source, Concept type, Concept destination, Concept characteristicType) { return buildDefaultRelationship(source, type, destination, characteristicType, getDefaultModuleConcept(), getDefaultNamespace()); } /** * @deprecated - use {@link SnomedComponents#newRelationship()} instead */ public Relationship buildDefaultRelationship(final Concept source, final Concept type, final Concept destination, Concept characteristicType, final Concept module, final String namespace) { // default is stated if (characteristicType == null) { characteristicType = findConceptById(STATED_RELATIONSHIP); } final Relationship relationship = buildEmptyRelationship(namespace); relationship.setType(type); relationship.setActive(true); relationship.setCharacteristicType(characteristicType); relationship.setSource(source); relationship.setDestination(destination); relationship.setGroup(0); relationship.setModifier(findConceptById(EXISTENTIAL_RESTRICTION_MODIFIER)); relationship.setModule(module); return relationship; } /** * Creates a relationship and sets its Id attribute to a new identifier. The relationship identifier will have the namespace * specified in the arguments. It is the caller's responsibility to set all other attributes. * * @param namespace the namespace of the relationship identifier * @return a relationship instance that only has a generated component identifier * @deprecated - will be replaced and removed in 4.4 */ public Relationship buildEmptyRelationship(final String namespace) { final Relationship relationship = SnomedFactory.eINSTANCE.createRelationship(); relationship.setId(generateComponentId(ComponentCategory.RELATIONSHIP, namespace)); return relationship; } /** * @param term * @param type * @param effectiveTime * @return a valid description populated with default values, the specified term and description type * @deprecated - will be replaced and removed in 4.4 */ public Description buildDefaultDescription(String term, String typeId) { return buildDefaultDescription(term, getDefaultNamespace(), findConceptById(typeId), getDefaultModuleConcept()); } /** * @param term * @param type * @param effectiveTime * @return a valid description populated with default values, the specified term and description type * @deprecated - will be replaced and removed in 4.4 */ public Description buildDefaultDescription(String term, final String namespace, final Concept type, final Concept moduleConcept) { return buildDefaultDescription(term, namespace, type, moduleConcept, "en"); } /*builds a description with the specified description type, module concept, language code and namespace.*/ private Description buildDefaultDescription(String term, final String namespace, final Concept type, final Concept moduleConcept, final String languageCode) { Description description = SnomedFactory.eINSTANCE.createDescription(); description.setId(generateComponentId(ComponentCategory.DESCRIPTION, namespace)); description.setActive(true); description.setCaseSignificance(findConceptById(ENTIRE_TERM_CASE_INSENSITIVE)); description.setType(type); description.setTerm(term); description.setLanguageCode(languageCode); description.setModule(moduleConcept); return description; } /** * Inactivates the given concepts and all relevant SNOMED CT component references these concepts. Inactivation reference set memberships * created when you perform the inactivation. This method return a plan to review the components which are going to be inactivated. * * @param monitor * @param conceptIds * @return the inactivation plan what should be executed to perform the inactivation process. */ public SnomedInactivationPlan inactivateConcepts(@Nullable final IProgressMonitor monitor, final CDOID... storageKeys) { return inactivateConcepts(createDefaultPlan(), monitor, storageKeys); } /** * Inactivates the given concepts and all relevant SNOMED CT component references these concepts. Inactivation reference set memberships * created when you perform the inactivation. This method return the given plan to review the components which are going to be inactivated. * * @param monitor * @param conceptIds * @return the inactivation plan what should be executed to perform the inactivation process. */ public SnomedInactivationPlan inactivateConcepts(SnomedInactivationPlan plan, @Nullable final IProgressMonitor monitor, final CDOID... storageKeys) { if (null != monitor) { monitor.beginTask("Creating inactivation plan...", storageKeys.length); } return internalInactivateConcept(plan, false, monitor, storageKeys); } /** * Inactivates the specified SNOMED CT concept and all references. Also creates the required reference set memberships based on the inactivation reason. * @param monitor progress monitor for the operation. * @param conceptId the identifier of the SNOMED CT concept to inactivate. * @return the inactivation plan what should be executed to perform the inactivation process. */ public SnomedInactivationPlan inactivateConcept(final IProgressMonitor monitor, final String... conceptId) { return internalInactivateConcept(createDefaultPlan(), true, monitor, findConceptById(conceptId[0]).cdoID()); } public SnomedInactivationPlan inactivateConcept(Inactivatable inactivatable) { return internalInactivateConcept(createDefaultPlan(), true, new NullProgressMonitor(), inactivatable.cdoID()); } private SnomedInactivationPlan createDefaultPlan() { return new SnomedInactivationPlan(this); } /*inactivates SNOMED CT concept identified by the specified CDO IDs.*/ private SnomedInactivationPlan internalInactivateConcept(SnomedInactivationPlan plan, final boolean updateSubtypeRelationships, final IProgressMonitor monitor, final CDOID... conceptCdoIds) { for (final CDOID cdoId : conceptCdoIds) { if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } final Concept concept = getConceptChecked(cdoId); if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } //concept plan.markForInactivation(concept); if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } //descriptions plan.markForInactivation(Iterables.toArray(concept.getDescriptions(), Description.class)); if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } plan.markForInactivation(FluentIterable.from(getInboundRelationshipsFromIndex(concept.getId())) .transform(new Function<SnomedRelationship, Long>() { @Override public Long apply(SnomedRelationship input) { return input.getStorageKey(); } }).toSet()); if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } // outbound relationships plan.markForInactivation(Iterables.toArray(concept.getOutboundRelationships(), Relationship.class)); if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } //reference set members final SnomedReferenceSetMembers members = SnomedRequests.prepareSearchMember().all() .filterByActive(true).filterByReferencedComponent(concept.getId()) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); for (final SnomedReferenceSetMember member : members) { if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } plan.markForInactivation(lookup(member.getId(), SnomedRefSetMember.class)); } // inactivate refset members if the inactivated concept is a reference set final SnomedReferenceSetMembers membersOfIdentifierConcept = SnomedRequests.prepareSearchMember().all() .filterByActive(true).filterByRefSet(concept.getId()) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); for (final SnomedReferenceSetMember member : membersOfIdentifierConcept) { if (monitor.isCanceled()) { return SnomedInactivationPlan.NULL_IMPL; } plan.markForInactivation(lookup(member.getId(), SnomedRefSetMember.class)); } monitor.worked(1); } return plan; } private Concept getConceptChecked(final CDOID cdoId) { final CDOObject object = transaction.getObject(cdoId); Preconditions.checkState(object instanceof Concept, "CDO object must be a SNOMED CT concept with ID: " + cdoId); return (Concept) object; } @Override public void delete(EObject object, boolean force) { if (object instanceof Concept) { tryDelete((Concept) object, force); } else if (object instanceof Description) { tryDelete((Description) object, force); } else if (object instanceof Relationship) { tryDelete((Relationship) object, force); } else if (object instanceof SnomedRefSet) { tryDelete((SnomedRefSet) object, force); } else if (object instanceof SnomedRefSetMember) { tryDelete((SnomedRefSetMember) object, force); } else if (object instanceof AttributeConstraint) { tryDelete((AttributeConstraint) object, force); } else { super.delete(object, force); } if (deletionPlan.isRejected()) { throw new ConflictException(deletionPlan.toString()); } } private void tryDelete(Concept concept, boolean force) { // Check if concept is already released, and this is not a forced delete if (concept.isReleased() && !force) { deletionPlan .addRejectionReason(String.format(COMPONENT_IS_RELEASED_MESSAGE, "concept", concept.getId())); return; } for (Relationship relationship : getInboundRelationships(concept.getId())) { if (relationship != null) { tryDelete(relationship, force); if (deletionPlan.isRejected()) { deletionPlan.addRejectionReason( String.format(UNABLE_TO_DELETE_CONCEPT_DUE_TO_RELEASED_INBOUND_RSHIP_MESSAGE, concept.getId(), relationship.getId())); return; } } } for (Description description : concept.getDescriptions()) { tryDelete(description, force); } for (Relationship outboundRelationship : concept.getOutboundRelationships()) { tryDelete(outboundRelationship, force); } SnomedRefSet refSet = lookupIfExists(concept.getId(), SnomedRefSet.class); if (refSet != null) { tryDelete(refSet, force); } deletionPlan.markForDeletion(concept); } private void tryDelete(Description description, boolean force) { // Check if description is already released, and this is not a forced delete if (description.isReleased() && !force) { deletionPlan.addRejectionReason( String.format(COMPONENT_IS_RELEASED_MESSAGE, "description", description.getId())); return; } deletionPlan.markForDeletion(description); } private void tryDelete(Relationship relationship, boolean force) { // Check if relationship is already released, and this is not a forced delete if (relationship.isReleased() && !force) { deletionPlan.addRejectionReason( String.format(COMPONENT_IS_RELEASED_MESSAGE, "relationship", relationship.getId())); return; } if (relationship.getSource() != null) { deletionPlan.markForDeletion(relationship); } } private void tryDelete(SnomedRefSet refSet, boolean force) { for (SnomedRefSetMember member : refSetEditingContext.getMembers(refSet)) { tryDelete(member, force); if (deletionPlan.isRejected()) { deletionPlan.addRejectionReason( String.format(UNABLE_TO_DELETE_REFERENCE_SET_MESSAGE, refSet.getIdentifierId())); return; } } deletionPlan.markForDeletion(refSet); } private void tryDelete(SnomedRefSetMember member, boolean force) { if (member.isReleased() && !force) { deletionPlan .addRejectionReason(String.format(COMPONENT_IS_RELEASED_MESSAGE, "member", member.getUuid())); return; } deletionPlan.markForDeletion(member); } private void tryDelete(AttributeConstraint object, boolean force) { deletionPlan.markForDeletion(object); } public final List<Relationship> getInboundRelationships(String conceptId) { return FluentIterable.from(getInboundRelationshipsFromIndex(conceptId)) .transform(new Function<SnomedRelationship, Relationship>() { @Override public Relationship apply(SnomedRelationship input) { return (Relationship) lookup(input.getStorageKey()); } }).toList(); } private Iterable<SnomedRelationship> getInboundRelationshipsFromIndex(String conceptId) { return SnomedRequests.prepareSearchRelationship().all().filterByDestination(conceptId) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); } /** * @return the module concept specified in the preferences, or falls back to the <em>SNOMED CT core module</em> * concept if the specified concept is not found. * @deprecated - use {@link ModulePreference} instead to get the module ID on client side */ public Concept getDefaultModuleConcept() { if (moduleConcept == null) { for (String modulePreference : ModulePreference.getModulePreference()) { if (moduleConcept != null) { break; } try { moduleConcept = getConcept(modulePreference); } catch (ComponentNotFoundException e) { // ignore and proceed to the next preference } } if (moduleConcept == null) { LOGGER.warn("Error while loading and caching SNOMED CT module concept."); } } return moduleConcept; } @Override @SuppressWarnings("unchecked") protected <T> ILookupService<T, CDOView> getComponentLookupService(Class<T> type) { if (type == Concept.class) { return (ILookupService<T, CDOView>) new SnomedConceptLookupService(); } else if (type == Description.class) { return (ILookupService<T, CDOView>) new SnomedDescriptionLookupService(); } else if (type == Relationship.class) { return (ILookupService<T, CDOView>) new SnomedRelationshipLookupService(); } else if (SnomedRefSet.class.isAssignableFrom(type)) { return (ILookupService<T, CDOView>) new SnomedRefSetLookupService(); } else if (SnomedRefSetMember.class.isAssignableFrom(type)) { return (ILookupService<T, CDOView>) new SnomedRefSetMemberLookupService(); } else if (AttributeConstraint.class.isAssignableFrom(type)) { return (ILookupService<T, CDOView>) new SnomedConstraintLookupService(); } else { return super.getComponentLookupService(type); } } /** * @return the currently configured namespace for use with component identifier generation */ public String getNamespace() { return nameSpace; } @Deprecated public static String getDefaultNamespace() { return getSnomedConfiguration().getNamespaces().getDefaultChildKey(); } public static SnomedConfiguration getSnomedConfiguration() { return ApplicationContext.getInstance().getService(SnomedConfiguration.class); } @Override public void preCommit() { if (deletionPlan.isRejected()) { throw new ConflictException(deletionPlan.toString()); } else if (!deletionPlan.isEmpty()) { final Set<String> deletedIds = deletionPlan.getDeletedItems().stream() .filter(Component.class::isInstance).map(Component.class::cast).map(Component::getId) .collect(Collectors.toSet()); if (!deletedIds.isEmpty()) { deletionPlan.markForDeletion(getReferringRefSetMembers(deletedIds)); } delete(); deletionPlan = new SnomedDeletionPlan(); } /* * Ensure that all new components (concepts, descriptions and relationships) * have unique IDs both among themselves and the components already persisted in * the database. Non-unique IDs will be overwritten with ones which are * guaranteed to be unique as of the time of this check. */ if (isUniquenessCheckEnabled()) { List<CDOIDAndVersion> newObjects = transaction.getChangeSetData().getNewObjects(); ComponentIdUniquenessValidator uniquenessEnforcer = new ComponentIdUniquenessValidator(this); for (CDOIDAndVersion newCdoIdAndVersion : newObjects) { CDOObject newObject = transaction.getObject(newCdoIdAndVersion.getID()); if (newObject instanceof Component) { Component newComponent = (Component) newObject; uniquenessEnforcer.validateAndReplaceComponentId(newComponent); } } } } private Set<SnomedRefSetMember> getReferringRefSetMembers(Iterable<String> deletedIds) { return SnomedRequests.prepareSearchMember().all().filterByReferencedComponent(deletedIds) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()).execute(getBus()).then(members -> { final Set<SnomedRefSetMember> referringMembersToAdd = members.getItems().stream() .map(member -> lookupIfExists(member.getStorageKey())) .filter(SnomedRefSetMember.class::isInstance).map(SnomedRefSetMember.class::cast) .collect(Collectors.toSet()); return referringMembersToAdd; }).then(referringMembers -> { referringMembers.addAll(getMembersByProps(deletedIds)); return referringMembers; }).then(referringMembers -> { for (SnomedRefSetMember member : referringMembers) { if (REFSET_DESCRIPTION_TYPE.equals(member.getRefSetIdentifierId())) { boolean hasRelatedDescriptions = SnomedRequests.prepareSearchDescription().setLimit(0) .filterById(member.getReferencedComponentId()) .build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync() .getTotal() > 0; if (hasRelatedDescriptions) { deletionPlan.addRejectionReason(UNABLE_TO_DELETE_DESCRIPTION_TYPE_CONCEPT_MESSAGE); } } } return referringMembers; }).getSync(); } private Set<SnomedRefSetMember> getMembersByProps(Iterable<String> deletedIds) { return RepositoryRequests.prepareBulkRead() .setBody(BulkRequest.<BranchContext>create() .add(getReferringMembersByProps(deletedIds, Fields.TARGET_COMPONENT)) // association .add(getReferringMembersByProps(deletedIds, Fields.VALUE_ID)) // attribute value .add(getReferringMembersByProps(deletedIds, Fields.UNIT_ID)) // cd .add(getReferringMembersByProps(deletedIds, Fields.CHARACTERISTIC_TYPE_ID)) // cd .add(getReferringMembersByProps(deletedIds, Fields.OPERATOR_ID)) // cd .add(getReferringMembersByProps(deletedIds, Fields.DESCRIPTION_FORMAT)) .add(getReferringMembersByProps(deletedIds, Fields.ACCEPTABILITY_ID)) // language .add(getReferringMembersByProps(deletedIds, Fields.MAP_TARGET)) // simple map .add(getReferringMembersByProps(deletedIds, Fields.CORRELATION_ID)) // complex map .add(getReferringMembersByProps(deletedIds, Fields.MAP_CATEGORY_ID)) // extended map .add(getReferringMembersByProps(deletedIds, Fields.MRCM_DOMAIN_ID)) // MRCM attr domain .add(getReferringMembersByProps(deletedIds, Fields.MRCM_RULE_STRENGTH_ID)) // MRCM attr domain/range .add(getReferringMembersByProps(deletedIds, Fields.MRCM_CONTENT_TYPE_ID)) // MRCM attr domain/range .add(getReferringMembersByProps(deletedIds, Fields.MRCM_RULE_REFSET_ID)) // MRCM module scope ).build(SnomedDatastoreActivator.REPOSITORY_UUID, getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)) .then(new Function<BulkResponse, SnomedReferenceSetMembers>() { @Override public SnomedReferenceSetMembers apply(BulkResponse input) { final List<SnomedReferenceSetMember> items = ImmutableList .copyOf(Iterables.concat(input.getResponses(SnomedReferenceSetMembers.class))); return new SnomedReferenceSetMembers(items, null, null, items.size(), items.size()); } }).getSync().stream().map(member -> lookupIfExists(member.getStorageKey())) .filter(SnomedRefSetMember.class::isInstance).map(SnomedRefSetMember.class::cast) .collect(Collectors.toSet()); } private SnomedRefSetMemberSearchRequestBuilder getReferringMembersByProps(Iterable<String> deletedIds, final String propField) { return SnomedRequests.prepareSearchMember().all() .filterByProps(OptionsBuilder.newBuilder().put(propField, deletedIds).build()); } /** * This functions deletes the objects in the deletionplan from the database. * For the sake of acceptable execution speed, the <code>remove(int index)</code> function is used * instead of the <code>remove(object)</code>. This way, the CDO will not iterate through the large * number of data in the resources. */ private void delete() { // organize elements regarding their index final Multimap<Integer, EObject> itemMap = ArrayListMultimap.create(); for (CDOObject item : deletionPlan.getDeletedItems()) { // Set bogus value here instead of letting it pass when trying to use it (it would silently remove the first member of a list) int index = -1; if (item instanceof Concept) { // from unordered concept wrapper index = getIndexFromDatabase(item, (Concepts) item.eContainer(), "SNOMED_CONCEPTS_CONCEPTS_LIST"); } else if (item instanceof SnomedRefSet) { // from cdoRootResource index = getIndexFromDatabase(item, item.cdoResource(), "ERESOURCE_CDORESOURCE_CONTENTS_LIST"); } else if (item instanceof AttributeConstraint) { // from mrcm concept model index = getIndexFromDatabase(item, (ConceptModel) item.eContainer(), "MRCM_CONCEPTMODEL_CONSTRAINTS_LIST"); } else if (item instanceof SnomedRefSetMember) { // from the refset list final SnomedRefSetMember member = (SnomedRefSetMember) item; if (null == member.eContainer()) { //if the reference set member has been detached from its container. continue; } final SnomedRefSet refSet = member.getRefSet(); if (!(refSet instanceof SnomedStructuralRefSet)) { // XXX: also includes the previous null check for refSet if (refSet instanceof SnomedMappingRefSet) { index = getIndexFromDatabase(item, ((SnomedRefSetMember) item).getRefSet(), "SNOMEDREFSET_SNOMEDMAPPINGREFSET_MEMBERS_LIST"); } else if (refSet instanceof SnomedRegularRefSet) { index = getIndexFromDatabase(item, ((SnomedRefSetMember) item).getRefSet(), "SNOMEDREFSET_SNOMEDREGULARREFSET_MEMBERS_LIST"); } else { throw new RuntimeException("Unknown reference set type"); } } } itemMap.put(index, item); } // iterate through the elements in reverse order for (Entry<Integer, EObject> toDelete : Ordering.from(new Comparator<Entry<Integer, EObject>>() { @Override public int compare(Entry<Integer, EObject> o1, Entry<Integer, EObject> o2) { return o1.getKey() - o2.getKey(); } }).reverse().sortedCopy(itemMap.entries())) { final EObject eObject = toDelete.getValue(); final int index = toDelete.getKey(); if (eObject instanceof Concept) { final Concepts concepts = (Concepts) eObject.eContainer(); concepts.getConcepts().remove(index); } else if (eObject instanceof SnomedRefSet) { refSetEditingContext.getContents().remove(index); } else if (eObject instanceof AttributeConstraint) { getConstraints().remove(index); } else if (eObject instanceof SnomedRefSetMember) { // get the refset and remove the member from its list SnomedRefSetMember member = (SnomedRefSetMember) eObject; SnomedRefSet refSet = member.getRefSet(); if (refSet != null) { if (refSet instanceof SnomedStructuralRefSet) { EcoreUtil.remove(member); } else if (refSet instanceof SnomedRegularRefSet) { ((SnomedRegularRefSet) refSet).getMembers().remove(index); } else { throw new IllegalStateException("Don't know how to remove member from reference set class '" + refSet.eClass().getName() + "'."); } } //in case of relationship or description an index lookup is not necessary } else if (eObject instanceof Relationship) { Relationship relationship = (Relationship) eObject; relationship.setSource(null); relationship.setDestination(null); } else if (eObject instanceof Description) { Description description = (Description) eObject; // maybe description was already removed before save, so the delete is reflected on the ui if (description.getConcept() != null) { description.setConcept(null); } } else { throw new IllegalArgumentException("Don't know how to delete " + eObject.eClass()); } } } public boolean isUniquenessCheckEnabled() { return uniquenessCheckEnabled; } public SnomedEditingContext setUniquenessCheckEnabled(boolean uniquenessCheckEnabled) { this.uniquenessCheckEnabled = uniquenessCheckEnabled; return this; } /** * Concept deletion may affect (other) concept descriptions because it was member of description type reference set. * Update the affected description types to synonym. * */ public void updateDescriptionTypes(Iterable<Description> dirtyDescriptions) { Concept synonym = findConceptById(SYNONYM); for (Description description : dirtyDescriptions) { description.setType(synonym); } } @Override protected String getRootResourceName() { return SnomedDatastoreActivator.ROOT_RESOURCE_NAME; } /** * Returns all referring reference set members for the given {@link Concept} * in attribute value, simple map and simple type reference sets. * * @param concept * @return * @throws NullPointerException * - if the given concept is <code>null</code> */ public Collection<SnomedRefSetMember> getReferringMembers(Concept concept) { return getRefSetEditingContext().getReferringMembers(concept); } /** * Generates a new SNOMED CT ID for the given component. The new ID will use * the currently set nameSpace, see {@link #getNamespace()}. * * @param component * - the component to generate ID for * @return * @see #getNamespace() * @throws NullPointerException * - if the given component was <code>null</code>. * @deprecated - use new {@link ISnomedIdentifierService} */ public String generateComponentId(Component component) { if (component instanceof Relationship) { return generateComponentId(ComponentCategory.RELATIONSHIP, getNamespace()); } else if (component instanceof Concept) { return generateComponentId(ComponentCategory.CONCEPT, getNamespace()); } else if (component instanceof Description) { return generateComponentId(ComponentCategory.DESCRIPTION, getNamespace()); } throw new IllegalArgumentException(MessageFormat.format("Unexpected component class ''{0}''.", component)); } public String generateComponentId(final ComponentCategory componentNature) { return generateComponentId(componentNature, getNamespace()); } public String generateComponentId(final ComponentCategory componentNature, final String namespace) { final IEventBus bus = ApplicationContext.getInstance().getServiceChecked(IEventBus.class); final String generatedId = SnomedRequests.identifiers().prepareGenerate().setCategory(componentNature) .setNamespace(namespace).build(SnomedDatastoreActivator.REPOSITORY_UUID).execute(bus).getSync() .first().get(); newComponentIds.add(generatedId); return generatedId; } private IEventBus getBus() { return ApplicationContext.getServiceForClass(IEventBus.class); } }