Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(snomed.reasoner): Reinstate equivalent concept merging extension #1277

Merged
merged 4 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.b2international.snowowl.core.api.SnowowlRuntimeException;
import com.b2international.snowowl.core.events.util.Promise;
import com.b2international.snowowl.core.events.util.Response;
import com.b2international.snowowl.eventbus.IEventBus;
import com.google.common.collect.Iterables;

import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -99,8 +100,10 @@ private Response<?> setDeferredResult(DeferredResult<ResponseEntity<?>> result,
// see Spring Security issue not being able to properly prevent duplicate caching headers
// https://github.com/spring-projects/spring-security/issues/12865
responseHeaders.forEach((entry) -> {
// XXX using set header here, for most of our use cases we only need a single response header, so overwrite anything that has been injected by Spring earlier
webRequest.getNativeResponse(HttpServletResponse.class).setHeader(entry.getKey(), entry.getValue());
if (!IEventBus.SEND_STACK_HEADER.equals(entry.getKey())) {
// XXX using set header here, for most of our use cases we only need a single response header, so overwrite anything that has been injected by Spring earlier
webRequest.getNativeResponse(HttpServletResponse.class).setHeader(entry.getKey(), entry.getValue());
}
});

result.setResult(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public final void handle(IMessage message) {
message.fail(e.getCause());
} catch (ApiException e) {
if (IEventBus.RECORD_SEND_STACK) {
System.err.println(message.headers().get("sendStack"));
System.err.println(message.headers().get(IEventBus.SEND_STACK_HEADER));
}

message.fail(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public interface IEventBus {
* Feature flag for send-time stack tracing
*/
boolean RECORD_SEND_STACK = Boolean.getBoolean("eventbus.record.stack");

/**
* The message header used for send-time call stack tracing
*/
String SEND_STACK_HEADER = "sendStack";

/**
* The address where handler registration and un-registration messages are sent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public IEventBus publish(String address, Object message, String tag, Map<String,

final String sendStack = sw.toString();
Map<String, String> headersWithStack = newHashMap(message.headers());
headersWithStack.put("sendStack", sendStack);
headersWithStack.put(SEND_STACK_HEADER, sendStack);
message.headers = Map.copyOf(headersWithStack);

} catch (IOException unexpected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@
import com.b2international.snowowl.snomed.datastore.converter.SnomedReferenceSetMemberConverter;
import com.b2international.snowowl.snomed.datastore.index.entry.SnomedDocument;
import com.b2international.snowowl.snomed.datastore.index.entry.SnomedRefSetMemberIndexEntry;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.*;

/**
* @since 4.5
Expand All @@ -53,6 +51,16 @@ public final class SnomedRefSetMemberSearchRequest extends SnomedSearchRequest<S

private static final long serialVersionUID = 1L;

// Requesting the "value" field should load "booleanValue", "decimalValue", "integerValue", "stringValue" and "dataType" instead
private static final Multimap<String, String> REPLACE_VALUE_FIELD = ImmutableMultimap.<String, String>builder()
.putAll(SnomedRf2Headers.FIELD_VALUE,
SnomedRefSetMemberIndexEntry.Fields.BOOLEAN_VALUE,
SnomedRefSetMemberIndexEntry.Fields.DECIMAL_VALUE,
SnomedRefSetMemberIndexEntry.Fields.INTEGER_VALUE,
SnomedRefSetMemberIndexEntry.Fields.STRING_VALUE,
SnomedRefSetMemberIndexEntry.Fields.DATA_TYPE)
.build();

private static final SortedMap<String, SnomedRefsetMemberFieldQueryHandler<?>> SUPPORTED_MEMBER_FIELDS = ImmutableSortedMap.<String, SnomedRefsetMemberFieldQueryHandler<?>>naturalOrder()
// String types, ECL support
.put(SnomedRf2Headers.FIELD_REFERENCED_COMPONENT_ID, new SnomedRefsetMemberFieldQueryHandler<>(String.class, SnomedRefSetMemberIndexEntry.Expressions::referencedComponentIds, true))
Expand Down Expand Up @@ -147,6 +155,11 @@ protected Class<SnomedRefSetMemberIndexEntry> getDocumentType() {
return SnomedRefSetMemberIndexEntry.class;
}

@Override
protected Multimap<String, String> collectFieldsToLoadReplacements() {
return REPLACE_VALUE_FIELD;
}

@Override
protected Expression prepareQuery(BranchContext context) {
final Collection<String> referencedComponentIds = getCollection(OptionKey.REFERENCED_COMPONENT, String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@
*/
final class SnomedRelationshipSearchRequest extends SnomedComponentSearchRequest<SnomedRelationships, SnomedRelationshipIndexEntry> {

// Requesting the "value" field should load "numericValue", "stringValue" and "valueType" instead
private static final Multimap<String, String> REPLACE_VALUE_FIELD = ImmutableMultimap.<String, String>builder()
.putAll(SnomedRelationship.Fields.VALUE, SnomedRelationshipIndexEntry.Fields.NUMERIC_VALUE, SnomedRelationshipIndexEntry.Fields.STRING_VALUE, SnomedRelationshipIndexEntry.Fields.VALUE_TYPE)
.putAll(SnomedRelationship.Fields.VALUE,
SnomedRelationshipIndexEntry.Fields.NUMERIC_VALUE,
SnomedRelationshipIndexEntry.Fields.STRING_VALUE,
SnomedRelationshipIndexEntry.Fields.VALUE_TYPE)
.build();

enum OptionKey {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
import com.b2international.index.Index;
import com.b2international.snowowl.core.RepositoryManager;
import com.b2international.snowowl.core.config.SnowOwlConfiguration;
import com.b2international.snowowl.core.plugin.ClassPathScanner;
import com.b2international.snowowl.core.plugin.Component;
import com.b2international.snowowl.core.repository.TerminologyRepositoryConfigurer;
import com.b2international.snowowl.core.setup.Environment;
import com.b2international.snowowl.core.setup.Plugin;
import com.b2international.snowowl.snomed.common.SnomedTerminologyComponentConstants;
import com.b2international.snowowl.snomed.datastore.config.SnomedCoreConfiguration;
import com.b2international.snowowl.snomed.reasoner.classification.ClassificationTracker;
import com.b2international.snowowl.snomed.reasoner.equivalence.IEquivalentConceptMerger;
import com.b2international.snowowl.snomed.reasoner.index.*;

/**
Expand All @@ -45,8 +47,10 @@ public void run(final SnowOwlConfiguration configuration, final Environment env)
final int maximumReasonerRuns = snomedConfig.getMaxReasonerRuns();
final long classificationCleanUpInterval = snomedConfig.getClassificationCleanUpInterval();
final ClassificationTracker classificationTracker = new ClassificationTracker(repositoryIndex, maximumReasonerRuns, TimeUnit.MINUTES.toMillis(classificationCleanUpInterval));

env.services().registerService(ClassificationTracker.class, classificationTracker);

final ClassPathScanner scanner = env.service(ClassPathScanner.class);
env.services().registerService(IEquivalentConceptMerger.Registry.class, new IEquivalentConceptMerger.Registry(scanner));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
package com.b2international.snowowl.snomed.reasoner.equivalence;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import com.b2international.snowowl.core.plugin.ClassPathScanner;
import com.b2international.snowowl.core.plugin.Component;
import com.b2international.snowowl.snomed.core.domain.SnomedConcept;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;

/**
* Implementations of this interface can define a customized way for
Expand All @@ -45,6 +50,16 @@ public interface IEquivalentConceptMerger {
*/
String PREFIX_UPDATED = "U_";

/**
* @return the name of this equivalent concept merging strategy (must be unique for each implementation)
*/
String getName();

/**
* @return the priority of the implementation, highest priority wins
*/
int getPriority();

/**
* Adds changes to the specified bulk request builder that effectively merges
* equivalent concepts into their corresponding suggested replacement.
Expand Down Expand Up @@ -84,11 +99,36 @@ public interface IEquivalentConceptMerger {
*
* @since 6.14
*/
@Component
class Default implements IEquivalentConceptMerger {

@Override
public String getName() {
return "default";
}

@Override
public int getPriority() {
return 0;
}

@Override
public Set<String> merge(final Multimap<SnomedConcept, SnomedConcept> equivalentConcepts) {
return Collections.emptySet();
}
}

final class Registry {
private final List<IEquivalentConceptMerger> implsByPriority;

public Registry(ClassPathScanner scanner) {
this.implsByPriority = Ordering.natural()
.onResultOf(IEquivalentConceptMerger::getPriority)
.immutableSortedCopy(scanner.getComponentsByInterface(IEquivalentConceptMerger.class));
}

public IEquivalentConceptMerger getHighestPriority() {
return Iterables.getLast(implsByPriority);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.validation.constraints.NotNull;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import jakarta.validation.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -49,7 +46,6 @@
import com.b2international.snowowl.core.identity.User;
import com.b2international.snowowl.core.internal.locks.DatastoreLockContextDescriptions;
import com.b2international.snowowl.core.locks.Locks;
import com.b2international.snowowl.core.plugin.Extensions;
import com.b2international.snowowl.core.repository.RepositoryRequests;
import com.b2international.snowowl.core.request.CommitResult;
import com.b2international.snowowl.snomed.common.SnomedRf2Headers;
Expand All @@ -69,6 +65,9 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

/**
* Represents a request that saves pre-recorded changes of a classification,
* usually running in a remote job.
Expand Down Expand Up @@ -439,15 +438,10 @@ private Set<String> mergeEquivalentConcepts(final BranchContext context,
return Collections.emptySet();
}

IEquivalentConceptMerger merger = Extensions.getFirstPriorityExtension(
IEquivalentConceptMerger.EXTENSION_POINT,
IEquivalentConceptMerger.class);
if (merger == null) {
merger = new IEquivalentConceptMerger.Default();
}

final String mergerName = merger.getClass().getSimpleName();
LOG.info("Reasoner service will use {} for equivalent concept merging.", mergerName);
final IEquivalentConceptMerger.Registry mergerRegistry = context.service(IEquivalentConceptMerger.Registry.class);
final IEquivalentConceptMerger merger = mergerRegistry.getHighestPriority();
final String mergerName = merger.getName();
LOG.info("Reasoner service will use '{}' implementation for equivalent concept merging.", mergerName);

final Set<String> conceptIdsToSkip = merger.merge(equivalentConcepts);
final Set<String> conceptIdsToKeep = equivalentConcepts.keySet()
Expand Down
Loading