Java tutorial
/* * Copyright 2011-2016 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.reasoner.server.classification; import static com.google.common.collect.Lists.newArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.ecore.util.EcoreUtil; import com.b2international.collections.longs.LongCollection; import com.b2international.collections.longs.LongIterator; import com.b2international.commons.options.OptionsBuilder; import com.b2international.snowowl.core.ApplicationContext; import com.b2international.snowowl.core.api.IBranchPath; import com.b2international.snowowl.core.api.SnowowlRuntimeException; import com.b2international.snowowl.core.exceptions.ConflictException; import com.b2international.snowowl.datastore.BranchPathUtils; import com.b2international.snowowl.datastore.utils.ComponentUtils2; import com.b2international.snowowl.eventbus.IEventBus; import com.b2international.snowowl.snomed.Concept; import com.b2international.snowowl.snomed.Relationship; import com.b2international.snowowl.snomed.core.domain.SnomedRelationships; import com.b2international.snowowl.snomed.datastore.SnomedDatastoreActivator; import com.b2international.snowowl.snomed.datastore.SnomedEditingContext; import com.b2international.snowowl.snomed.datastore.SnomedInactivationPlan; import com.b2international.snowowl.snomed.datastore.SnomedInactivationPlan.InactivationReason; import com.b2international.snowowl.snomed.datastore.SnomedRefSetEditingContext; import com.b2international.snowowl.snomed.datastore.id.SnomedIdentifiers; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedRefSetMemberIndexEntry; import com.b2international.snowowl.snomed.datastore.model.SnomedModelExtensions; import com.b2international.snowowl.snomed.datastore.request.SnomedRequests; import com.b2international.snowowl.snomed.snomedrefset.SnomedAssociationRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedAttributeValueRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedConcreteDataTypeRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedMappingRefSet; import com.b2international.snowowl.snomed.snomedrefset.SnomedRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedRegularRefSet; import com.b2international.snowowl.snomed.snomedrefset.SnomedSimpleMapRefSetMember; import com.b2international.snowowl.snomed.snomedrefset.SnomedStructuralRefSet; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; /** * */ public class EquivalentConceptMerger { private final SnomedEditingContext editingContext; private final List<LongCollection> equivalenciesToFix; public EquivalentConceptMerger(final SnomedEditingContext editingContext, final List<LongCollection> equivalenciesToFix) { this.editingContext = editingContext; this.equivalenciesToFix = equivalenciesToFix; } public void fixEquivalencies() { // First resolve the equivalencies Map, to find the equivalent concept instances final Multimap<Concept, Concept> equivalentConcepts = resolveEquivalencies(); final Iterable<Concept> concepts = Iterables.concat(equivalentConcepts.keySet(), equivalentConcepts.values()); final Iterable<String> destinationIds = Iterables.transform(concepts, Concept::getId); SnomedRelationships inboundRelationships = SnomedRequests.prepareSearchRelationship().all() .filterByActive(true).filterByDestination(destinationIds) .build(SnomedDatastoreActivator.REPOSITORY_UUID, editingContext.getBranch()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync(); final Multimap<String, Relationship> inboundRelationshipMap = HashMultimap.create(FluentIterable .from(inboundRelationships) .transform(relationship -> (Relationship) editingContext.lookup(relationship.getStorageKey())) //Exclude relationships that were already marked redundant .filter(relationship -> relationship.getSource() != null && relationship.getDestination() != null) .index(relationship -> relationship.getDestination().getId())); for (Relationship relationship : ComponentUtils2.getNewObjects(editingContext.getTransaction(), Relationship.class)) { inboundRelationshipMap.put(relationship.getDestination().getId(), relationship); } for (Relationship relationship : ComponentUtils2.getDetachedObjects(editingContext.getTransaction(), Relationship.class)) { inboundRelationshipMap.values().remove(relationship); } // iterate over the sorted concepts and switch to the equivalent using // the resolved Map try { for (final Concept conceptToKeep : equivalentConcepts.keySet()) { final Collection<Concept> conceptsToRemove = equivalentConcepts.get(conceptToKeep); switchToEquivalentConcept(conceptToKeep, conceptsToRemove, inboundRelationshipMap, equivalentConcepts); removeOrDeactivate(conceptsToRemove); } } catch (ConflictException e) { throw new SnowowlRuntimeException(e); } } private void removeOrDeactivate(Collection<Concept> conceptsToRemove) { if (!Iterables.isEmpty(conceptsToRemove)) { final SnomedInactivationPlan plan = new SnomedInactivationPlan(this.editingContext); for (final Concept concept : conceptsToRemove) { if (concept.isReleased()) { this.editingContext.inactivateConcepts(plan, new NullProgressMonitor(), concept.cdoID()); } else { this.editingContext.delete(concept, false); } } plan.performInactivation(InactivationReason.RETIRED, null); } } /** * Resolves the equivalency {@link Map} to a {@link Map} that contains only * {@link Concept} instances. The returned {@link Map} is contains value * {@link Concept}s as replacement for the key {@link Concept}s. * * @param equivalencies * @param results * @param helper * @return */ private Multimap<Concept, Concept> resolveEquivalencies() { final Multimap<Concept, Concept> processedEquivalencies = HashMultimap.create(); for (final LongCollection equivalentSet : equivalenciesToFix) { final List<Concept> conceptsToRemove = Lists.newArrayList(); for (final LongIterator itr = equivalentSet.iterator(); itr.hasNext(); /* empty */) { final long conceptId = itr.next(); final Concept concept = editingContext.lookup(Long.toString(conceptId), Concept.class); conceptsToRemove.add(concept); } final Concept conceptToKeep = Ordering.natural().onResultOf(new Function<Concept, Long>() { @Override public Long apply(Concept input) { return CDOIDUtil.getLong(input.cdoID()); } }).min(conceptsToRemove); conceptsToRemove.remove(conceptToKeep); processedEquivalencies.putAll(conceptToKeep, conceptsToRemove); } return processedEquivalencies; } private void switchToEquivalentConcept(final Concept conceptToKeep, final Collection<Concept> conceptsToRemove, Multimap<String, Relationship> inboundRelationshipMap, Multimap<Concept, Concept> equivalentConcepts) { removeDeprecatedRelationships(conceptToKeep, conceptsToRemove, inboundRelationshipMap); for (final Concept conceptToRemove : conceptsToRemove) { switchInboundRelationships(conceptToKeep, conceptsToRemove, conceptToRemove, inboundRelationshipMap, equivalentConcepts); switchOutboundRelationships(conceptToKeep, conceptsToRemove, conceptToRemove, inboundRelationshipMap, equivalentConcepts); switchRefSetMembers(conceptToKeep, conceptToRemove); } } private void switchOutboundRelationships(final Concept conceptToKeep, final Collection<Concept> conceptsToRemove, final Concept conceptToRemove, Multimap<String, Relationship> inboundRelationshipMap, Multimap<Concept, Concept> equivalentConcepts) { for (final Relationship relationshipToRemove : newArrayList(conceptToRemove.getOutboundRelationships())) { boolean found = false; for (final Relationship replacementOutboundRelationship : conceptToKeep.getOutboundRelationships()) { if (isEquivalentOutboundRelationship(equivalentConcepts, relationshipToRemove, replacementOutboundRelationship)) { found = true; break; } } if (!found) { if (!conceptsToRemove.contains(relationshipToRemove.getSource())) { final String namespace = SnomedIdentifiers.create(relationshipToRemove.getId()).getNamespace(); final Relationship newRelationship = editingContext.buildDefaultRelationship(conceptToKeep, relationshipToRemove.getType(), relationshipToRemove.getDestination(), relationshipToRemove.getCharacteristicType(), relationshipToRemove.getModule(), namespace); inboundRelationshipMap.put(newRelationship.getDestination().getId(), newRelationship); switchRelationship(newRelationship, relationshipToRemove); } } if (inboundRelationshipMap.containsValue(relationshipToRemove)) { inboundRelationshipMap.remove(relationshipToRemove.getDestination().getId(), relationshipToRemove); } SnomedModelExtensions.removeOrDeactivate(relationshipToRemove); } } private void switchInboundRelationships(final Concept conceptToKeep, final Collection<Concept> conceptsToRemove, final Concept conceptToRemove, Multimap<String, Relationship> inboundRelationshipMap, Multimap<Concept, Concept> equivalentConcepts) { for (final Relationship relationshipToRemove : newArrayList( inboundRelationshipMap.get(conceptToRemove.getId()))) { boolean found = false; for (final Relationship replacementInboundRelationship : Sets .newHashSet(inboundRelationshipMap.get(conceptToKeep.getId()))) { if (isEquivalentInboundRelationship(equivalentConcepts, relationshipToRemove, replacementInboundRelationship)) { found = true; break; } } if (!found) { if (!conceptsToRemove.contains(relationshipToRemove.getSource())) { final String namespace = SnomedIdentifiers.create(relationshipToRemove.getId()).getNamespace(); final Relationship newRelationship = editingContext.buildDefaultRelationship( relationshipToRemove.getSource(), relationshipToRemove.getType(), conceptToKeep, relationshipToRemove.getCharacteristicType(), relationshipToRemove.getModule(), namespace); inboundRelationshipMap.put(newRelationship.getDestination().getId(), newRelationship); switchRelationship(newRelationship, relationshipToRemove); } } if (inboundRelationshipMap.containsValue(relationshipToRemove)) { inboundRelationshipMap.remove(relationshipToRemove.getDestination().getId(), relationshipToRemove); } SnomedModelExtensions.removeOrDeactivate(relationshipToRemove); } } private void switchRelationship(Relationship relationshipToKeep, Relationship relationshipToRemove) { relationshipToKeep.setCharacteristicType(relationshipToRemove.getCharacteristicType()); switchConcreteDomains(relationshipToKeep, relationshipToRemove); } private void switchConcreteDomains(Relationship relationshipToKeep, Relationship relationshipToRemove) { for (SnomedConcreteDataTypeRefSetMember member : relationshipToRemove.getConcreteDomainRefSetMembers()) { if (!member.isActive()) { continue; } final SnomedConcreteDataTypeRefSetMember newMember = EcoreUtil.copy(member); newMember.setUuid(UUID.randomUUID().toString()); newMember.unsetEffectiveTime(); newMember.setReferencedComponentId(relationshipToKeep.getId()); relationshipToKeep.getConcreteDomainRefSetMembers().add(newMember); } } private void switchRefSetMembers(final Concept conceptToKeep, final Concept conceptToRemove) { final String id = conceptToKeep.getId(); final SnomedRefSetEditingContext refSetEditingContext = editingContext.getRefSetEditingContext(); final IBranchPath branchPath = BranchPathUtils.createPath(editingContext.getTransaction()); for (final SnomedRefSetMember referringMemberToRemove : newArrayList( editingContext.getReferringMembers(conceptToRemove))) { if (!referringMemberToRemove.isActive()) { continue; } if (referringMemberToRemove instanceof SnomedSimpleMapRefSetMember) { final SnomedSimpleMapRefSetMember simpleMapMember = (SnomedSimpleMapRefSetMember) referringMemberToRemove; if (!hasMapping(branchPath, simpleMapMember.getRefSetIdentifierId(), id, simpleMapMember.getMapTargetComponentId())) { final SnomedSimpleMapRefSetMember newMember = refSetEditingContext.createSimpleMapRefSetMember( id, simpleMapMember.getMapTargetComponentId(), simpleMapMember.getModuleId(), (SnomedMappingRefSet) simpleMapMember.getRefSet()); newMember.setMapTargetComponentDescription(simpleMapMember.getMapTargetComponentDescription()); ((SnomedMappingRefSet) simpleMapMember.getRefSet()).getMembers().add(newMember); } else { SnomedModelExtensions.removeOrDeactivate(referringMemberToRemove); } } else { if (!isActiveMemberOf(branchPath, referringMemberToRemove.getRefSetIdentifierId(), id)) { final SnomedRefSetMember newMember = EcoreUtil.copy(referringMemberToRemove); newMember.unsetEffectiveTime(); newMember.setUuid(UUID.randomUUID().toString()); newMember.setReferencedComponentId(id); if (newMember.getRefSet() instanceof SnomedRegularRefSet) { ((SnomedRegularRefSet) newMember.getRefSet()).getMembers().add(newMember); } else if (newMember.getRefSet() instanceof SnomedStructuralRefSet) { if (newMember instanceof SnomedConcreteDataTypeRefSetMember) { conceptToKeep.getConcreteDomainRefSetMembers() .add((SnomedConcreteDataTypeRefSetMember) newMember); } else if (newMember instanceof SnomedAttributeValueRefSetMember) { conceptToKeep.getInactivationIndicatorRefSetMembers() .add((SnomedAttributeValueRefSetMember) newMember); } else if (newMember instanceof SnomedAssociationRefSetMember) { conceptToKeep.getAssociationRefSetMembers() .add((SnomedAssociationRefSetMember) newMember); } } } else { SnomedModelExtensions.removeOrDeactivate(referringMemberToRemove); } } } } private boolean isActiveMemberOf(IBranchPath branchPath, String refSetIdentifierId, String referencedComponentId) { return SnomedRequests.prepareSearchMember().setLimit(0).filterByActive(true) .filterByRefSet(refSetIdentifierId).filterByReferencedComponent(referencedComponentId) .build(SnomedDatastoreActivator.REPOSITORY_UUID, branchPath.getPath()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync().getTotal() > 0; } private boolean hasMapping(IBranchPath branchPath, String refSetIdentifierId, String referencedComponentId, String mapTargetComponentId) { return SnomedRequests.prepareSearchMember().setLimit(0).filterByActive(true) .filterByRefSet(refSetIdentifierId).filterByReferencedComponent(referencedComponentId) .filterByProps(OptionsBuilder.newBuilder() .put(SnomedRefSetMemberIndexEntry.Fields.MAP_TARGET, mapTargetComponentId).build()) .build(SnomedDatastoreActivator.REPOSITORY_UUID, branchPath.getPath()) .execute(ApplicationContext.getServiceForClass(IEventBus.class)).getSync().getTotal() > 0; } private void removeDeprecatedRelationships(final Concept conceptToKeep, final Collection<Concept> conceptsToRemove, final Multimap<String, Relationship> inboundRelationshipMap) { for (final Relationship inboundRelationship : Sets .newHashSet(inboundRelationshipMap.get(conceptToKeep.getId()))) { if (conceptsToRemove.contains(inboundRelationship.getSource())) { if (inboundRelationshipMap.containsValue(inboundRelationship)) { inboundRelationshipMap.remove(inboundRelationship.getDestination().getId(), inboundRelationship); } SnomedModelExtensions.removeOrDeactivate(inboundRelationship); } } for (final Relationship outboundRelationship : newArrayList(conceptToKeep.getOutboundRelationships())) { if (conceptsToRemove.contains(outboundRelationship.getDestination())) { if (inboundRelationshipMap.containsValue(outboundRelationship)) { inboundRelationshipMap.remove(outboundRelationship.getDestination().getId(), outboundRelationship); } SnomedModelExtensions.removeOrDeactivate(outboundRelationship); } } } private boolean isEquivalentInboundRelationship(final Multimap<Concept, Concept> equivalentConcepts, Relationship relationshipToRemove, Relationship relationshipToKeep) { Concept sourceToKeep = relationshipToKeep.getSource(); Concept sourceToRemove = relationshipToRemove.getSource(); return relationshipToRemove.getType().equals(relationshipToKeep.getType()) && (sourceToRemove.equals(sourceToKeep) || equivalentConcepts.get(sourceToKeep).contains(sourceToRemove)) && relationshipToRemove.getCharacteristicType().equals(relationshipToKeep.getCharacteristicType()) && relationshipToRemove.getModifier().equals(relationshipToKeep.getModifier()); } private boolean isEquivalentOutboundRelationship(final Multimap<Concept, Concept> equivalentConcepts, Relationship relationshipToRemove, Relationship relationshipToKeep) { Concept destinationToKeep = relationshipToKeep.getDestination(); Concept destinationToRemove = relationshipToRemove.getDestination(); return relationshipToRemove.getType().equals(relationshipToKeep.getType()) && (destinationToRemove.equals(destinationToKeep) || equivalentConcepts.get(destinationToKeep).contains(destinationToRemove)) && relationshipToRemove.getCharacteristicType().equals(relationshipToKeep.getCharacteristicType()) && relationshipToRemove.getModifier().equals(relationshipToKeep.getModifier()); } }