Skip to content

Commit

Permalink
ISTO-117 Multiple language support.
Browse files Browse the repository at this point in the history
  • Loading branch information
kaicode committed Apr 2, 2024
1 parent b378d0a commit 21eec0a
Show file tree
Hide file tree
Showing 43 changed files with 713 additions and 95 deletions.
6 changes: 4 additions & 2 deletions docs/running-with-java.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ The following examples use the version URI for the January 2024 International Ed
[See "URIs for Editions and Versions" in the SNOMED CT URI Standard](http:https://snomed.org/uri).

#### Option 1: Loading via the command line
Use the `--load` parameter with the path to a SNOMED CT Edition RF2 archive
and the `--version-uri` parameter with the URI of that SNOMED CT Edition version.
Use the `--load` parameter with the path to a SNOMED CT Edition RF2 archive.
Or alternatively a comma separated list of RF2 paths including an Edition and one or more Extension packages.

Use the `--version-uri` parameter with the URI of that SNOMED CT Edition version.

For example:
```
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@
<artifactId>lucene-join</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>8.11.3</version>
</dependency>

<!-- Logging -->
<dependency>
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A fast FHIR Terminology Server for SNOMED CT with a small memory footprint.
- Perfect for search
- Most relevant results first
- Supports terminology binding
- Multiple language support with configurable character folding
- FHIR Terminology Operations
- CodeSystem lookup
- Including parents, children, designations, normal form
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.snomed.snowstormlite.config;

import java.util.*;

public class LanguageCharacterFoldingConfiguration {

private final Map<String, String> charactersNotFolded = new HashMap<>();
private Map<String, Set<Character>> charactersNotFoldedSets;

public Map<String, String> getCharactersNotFolded() {
return charactersNotFolded;
}

public Map<String, Set<Character>> getCharactersNotFoldedSets() {
if (charactersNotFoldedSets == null) {
charactersNotFoldedSets = buildMap();
}
return charactersNotFoldedSets;
}

public Set<Character> getCharactersNotFolded(String lang) {
return getCharactersNotFoldedSets().getOrDefault(lang, Collections.emptySet());
}

private synchronized Map<String, Set<Character>> buildMap() {
Map<String, Set<Character>> notFoldedSets = new HashMap<>();
for (Map.Entry<String, String> entry : charactersNotFolded.entrySet()) {
notFoldedSets.put(entry.getKey(), toCharSet(entry.getValue()));
}
return notFoldedSets;
}

private Set<Character> toCharSet(String s) {
Set<Character> set = new HashSet<>();
for (char c : s.toCharArray()) {
set.add(c);
}
return set;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.snomed.snowstormlite.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snomed.snowstormlite.domain.LanguageDialect;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class LanguageDialectAliasConfiguration {

private static LanguageDialectAliasConfiguration singleton;

private final Logger logger = LoggerFactory.getLogger(getClass());

private final Map<String, String> config = new HashMap<>();

private Map<String, DialectConfiguration> dialects;

// Used by Spring to fill the properties
public Map<String, String> getConfig() {
return config;
}

@PostConstruct
private void init() {
Map<String, DialectConfiguration> dialects = new HashMap<>();
for (String key : config.keySet()) {
String dialectCode = key.substring(key.lastIndexOf(".") + 1).toLowerCase();
long languageRefsetId = Long.parseLong(config.get(key));
dialects.put(dialectCode, new DialectConfiguration(dialectCode, languageRefsetId));
logger.info("Known dialect " + dialectCode + " - refset: " + languageRefsetId);
}
this.dialects = Collections.unmodifiableMap(dialects);
singleton = this;
}

public static LanguageDialectAliasConfiguration instance() {
return singleton;
}

public Long findRefsetForDialect(String dialectCode) {
if (dialects.containsKey(dialectCode)) {
return dialects.get(dialectCode).languageRefsetId;
}
return null;
}

public LanguageDialect getLanguageDialect(String dialectCode) {
//Do we know about this language code?
dialectCode = dialectCode.toLowerCase();
if (dialects.containsKey(dialectCode)) {
String lang = dialectCode.split("-")[0];
Long refsetId = findRefsetForDialect(dialectCode);
return new LanguageDialect(lang, refsetId);
}
return new LanguageDialect(dialectCode);
}

public void report() {
logger.info(dialects.size() + " known dialects configured");
}

public static class DialectConfiguration {
String dialectCode;
Long languageRefsetId;

DialectConfiguration(String dialectCode, Long languageRefsetId) {
this.dialectCode = dialectCode;
this.languageRefsetId = languageRefsetId;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.snomed.snowstormlite.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ServiceConfiguration {

@Bean
@ConfigurationProperties(prefix = "search.language")
public LanguageCharacterFoldingConfiguration languageCharacterFoldingConfiguration() {
return new LanguageCharacterFoldingConfiguration();
}

@Bean
@ConfigurationProperties(prefix = "search.dialect")
public LanguageDialectAliasConfiguration languageDialectAliasConfiguration() {
return new LanguageDialectAliasConfiguration();
}

}
4 changes: 4 additions & 0 deletions src/main/java/org/snomed/snowstormlite/domain/Concepts.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.snomed.snowstormlite.domain;

import java.util.List;

public class Concepts {

public static final String FSN = "900000000000003001";
Expand All @@ -12,4 +14,6 @@ public class Concepts {
public static final String ACCEPTABLE = "900000000000549004";
public static final String DEFINED = "900000000000073002";
public static final long REFERENCE_SET_ATTRIBUTE = 900000000000457003L;
public static final long US_LANG_REFSET = 900000000000509007L;
public static final List<LanguageDialect> DEFAULT_LANGUAGE = List.of(new LanguageDialect("en", US_LANG_REFSET));
}
23 changes: 16 additions & 7 deletions src/main/java/org/snomed/snowstormlite/domain/FHIRConcept.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ public FHIRConcept(String conceptId, String effectiveTime, boolean active, Strin
this.defined = defined;
}

public Parameters toHapi(FHIRCodeSystem codeSystem, TermProvider termProvider) throws IOException {
public Parameters toHapi(FHIRCodeSystem codeSystem, TermProvider termProvider, List<LanguageDialect> languageDialects) throws IOException {
Parameters parameters = new Parameters();
parameters.addParameter(new Parameters.ParametersParameterComponent().setName("code").setValue(new CodeType(getConceptId())));
parameters.addParameter("display", getPT());
parameters.addParameter("display", getPT(languageDialects));
parameters.addParameter("name", codeSystem.getTitle());
parameters.addParameter(new Parameters.ParametersParameterComponent().setName("system").setValue(new UriType(FHIRConstants.SNOMED_URI)));
parameters.addParameter(new Parameters.ParametersParameterComponent().setName("version").setValue(new StringType(codeSystem.getVersionUri())));
Expand Down Expand Up @@ -113,7 +113,7 @@ public Parameters toHapi(FHIRCodeSystem codeSystem, TermProvider termProvider) t

if (active) {
parameters.addParameter(createProperty("normalFormTerse", getNormalFormTerse(), false));
parameters.addParameter(createProperty("normalForm", getNormalForm(termProvider), false));
parameters.addParameter(createProperty("normalForm", getNormalForm(termProvider, languageDialects), false));
}

return parameters;
Expand All @@ -136,11 +136,11 @@ public void addMapping(FHIRMapping mapping) {
}

public String getNormalFormTerse() throws IOException {
return getNormalForm(null);
return getNormalForm(null, null);
}

public String getNormalForm(TermProvider termProvider) throws IOException {
return NormalFormBuilder.getNormalForm(this, termProvider);
public String getNormalForm(TermProvider termProvider, List<LanguageDialect> languageDialects) throws IOException {
return NormalFormBuilder.getNormalForm(this, termProvider, languageDialects);
}

private String getLangAndRefsetCode(String lang, String preferredLangRefset) {
Expand All @@ -157,7 +157,16 @@ private String getLangAndRefsetCode(String lang, String preferredLangRefset) {
return format("%s-x-sctlang-%s", lang, builder);
}

public String getPT() {
public String getPT(List<LanguageDialect> displayLanguages) {
for (LanguageDialect displayLanguage : displayLanguages) {
for (FHIRDescription description : descriptions) {
Long displayLangRefset = displayLanguage.getLanguageReferenceSet();
if (!description.isFsn() && description.getLang().equals(displayLanguage.getLanguageCode())
&& (displayLangRefset == null || description.getPreferredLangRefsets().contains(displayLangRefset.toString()))) {
return description.getTerm();
}
}
}
for (FHIRDescription description : descriptions) {
if (!description.isFsn() && !description.getPreferredLangRefsets().isEmpty()) {
return description.getTerm();
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/snomed/snowstormlite/domain/FHIRDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ public class FHIRDescription {
private boolean fsn;

private Set<String> preferredLangRefsets;
private Set<String> acceptableLangRefsets;
private FHIRConcept concept;

public FHIRDescription() {
id = UUID.randomUUID().toString();
preferredLangRefsets = new HashSet<>();
acceptableLangRefsets = new HashSet<>();
}

public FHIRDescription(String id, String languageCode, boolean fsn, String term) {
Expand Down Expand Up @@ -70,6 +73,23 @@ public void setPreferredLangRefsets(Set<String> preferredLangRefsets) {
this.preferredLangRefsets = preferredLangRefsets;
}

public Set<String> getAcceptableLangRefsets() {
return acceptableLangRefsets;
}

public void setAcceptableLangRefsets(Set<String> acceptableLangRefsets) {
this.acceptableLangRefsets = acceptableLangRefsets;
}

@JsonIgnore
public FHIRConcept getConcept() {
return concept;
}

public void setConcept(FHIRConcept concept) {
this.concept = concept;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/org/snomed/snowstormlite/domain/LanguageDialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.snomed.snowstormlite.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class LanguageDialect {

private String languageCode;
private Long languageReferenceSet;

public LanguageDialect() { }

public LanguageDialect(String languageCode) {
this.languageCode = languageCode;
}

public LanguageDialect(String languageCode, Long languageReferenceSet) {
this.languageCode = languageCode;
this.languageReferenceSet = languageReferenceSet;
}

public String getLanguageCode() {
return languageCode;
}

public Long getLanguageReferenceSet() {
return languageReferenceSet;
}

public static List<String> toLanguageCodes(List<LanguageDialect> languageDialects) {
List<String> languageCodes = new ArrayList<>();
if (languageDialects != null) {
for (LanguageDialect languageDialect : languageDialects) {
String code = languageDialect.getLanguageCode();
if (code != null && !code.isEmpty()) {
languageCodes.add(code);
}
}
}
return languageCodes;
}

public String toString() {
String str = "";
if (languageCode != null) {
str = languageCode;
if (languageReferenceSet != null) {
str += " - ";
}
}

if (languageReferenceSet != null) {
str += languageReferenceSet;
}
return str;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LanguageDialect that = (LanguageDialect) o;
return languageCode.equals(that.languageCode) &&
Objects.equals(languageReferenceSet, that.languageReferenceSet);
}

@Override
public int hashCode() {
return Objects.hash(languageCode, languageReferenceSet);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ca.uhn.fhir.rest.server.IResourceProvider;
import org.hl7.fhir.r4.model.*;
import org.snomed.snowstormlite.domain.FHIRCodeSystem;
import org.snomed.snowstormlite.domain.LanguageDialect;
import org.snomed.snowstormlite.service.CodeSystemRepository;
import org.snomed.snowstormlite.service.CodeSystemService;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -17,8 +18,8 @@
import java.util.List;

import static java.lang.String.format;
import static org.snomed.snowstormlite.fhir.FHIRConstants.ACCEPT_LANGUAGE_HEADER;
import static org.snomed.snowstormlite.fhir.FHIRHelper.*;
import static org.snomed.snowstormlite.service.Constants.ACCEPT_LANGUAGE_HEADER;

@Component
public class CodeSystemProvider implements IResourceProvider {
Expand All @@ -29,6 +30,9 @@ public class CodeSystemProvider implements IResourceProvider {
@Autowired
private CodeSystemRepository codeSystemRepository;

@Autowired
private LanguageDialectParser languageDialectParser;

@Search
public List<CodeSystem> findCodeSystems(
@OptionalParam(name="id") String id,
Expand Down Expand Up @@ -80,7 +84,8 @@ public Parameters lookupImplicit(
mutuallyExclusive("code", code, "coding", coding);
notSupported("date", date);
FHIRCodeSystem codeSystem = getCodeSystemVersionOrThrow(system, version, coding);
return codeSystemService.lookup(codeSystem, recoverCode(code, coding), displayLanguage, request.getHeader(ACCEPT_LANGUAGE_HEADER), propertiesType);
List<LanguageDialect> languageDialects = languageDialectParser.parseDisplayLanguageWithDefaultFallback(displayLanguage, request.getHeader(ACCEPT_LANGUAGE_HEADER));
return codeSystemService.lookup(codeSystem, recoverCode(code, coding), languageDialects);
}

private FHIRCodeSystem getCodeSystemVersionOrThrow(UriType system, StringType version, Coding coding) {
Expand Down
Loading

0 comments on commit 21eec0a

Please sign in to comment.