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.request; import static com.google.common.collect.Maps.newHashMap; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import com.b2international.commons.ExplicitFirstOrdering; import com.b2international.commons.http.ExtendedLocale; import com.b2international.snowowl.snomed.SnomedConstants.Concepts; import com.b2international.snowowl.snomed.core.domain.Acceptability; import com.b2international.snowowl.snomed.core.domain.SnomedDescription; import com.b2international.snowowl.snomed.core.domain.SnomedDescriptions; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; /** * @since 4.5 */ public abstract class DescriptionRequestHelper { private static class ExtractBestFunction<T> implements Function<Collection<SnomedDescription>, SnomedDescription> { private final List<T> orderedValues; private final Function<SnomedDescription, T> valuesExtractor; private final Ordering<SnomedDescription> ordering; private ExtractBestFunction(List<T> orderedValues, Function<SnomedDescription, T> valuesExtractor) { this.orderedValues = orderedValues; this.valuesExtractor = valuesExtractor; this.ordering = ExplicitFirstOrdering.create(orderedValues).onResultOf(valuesExtractor); } @Override public SnomedDescription apply(Collection<SnomedDescription> descriptions) { try { SnomedDescription candidate = ordering.min(descriptions); /* * We're using ExplicitFirstOrdering so that it doesn't break in the middle of processing * the collection, but this means that we have to test the final SnomedDescription again * to see if it is suitable for our needs. */ if (orderedValues.contains(valuesExtractor.apply(candidate))) { return candidate; } else { return null; } } catch (NoSuchElementException e) { return null; } } } /** * Retrieves the preferred term for the concept identified by the given identifier, if it exists. * <p> * The first active description with "synonym" or descendant as the type will be returned, where all of the following conditions apply: * <ul> * <li>a matching well-known language reference set exists for the given {@code ExtendedLocale} (eg. {@code "en-GB"}); * <li>the description has a language reference set member in the reference set identified above with preferred acceptability. * </ul> * <p> * If no such description can be found, the process is repeated with the next {@code Locale} in the list. * * @param conceptId the identifier of the concept for which the preferred term should be returned (may not be {@code null}) * @param locales a list of {@link Locale}s to use, in order of preference * @return the preferred term for the concept, or {@code null} if no results could be retrieved */ public SnomedDescription getPreferredTerm(String conceptId, List<ExtendedLocale> locales) { SnomedDescriptionSearchRequestBuilder req = preparePtSearch(conceptId, locales); SnomedDescriptions descriptions = execute(req); Map<String, SnomedDescription> bestMatchByConceptId = indexBestPreferredByConceptId(descriptions, locales); return bestMatchByConceptId.get(conceptId); } public Map<String, SnomedDescription> getPreferredTerms(Set<String> conceptIds, List<ExtendedLocale> locales) { if (conceptIds.isEmpty()) { return Collections.emptyMap(); } SnomedDescriptionSearchRequestBuilder req = preparePtSearch(conceptIds, locales); SnomedDescriptions descriptions = execute(req); Map<String, SnomedDescription> bestMatchByConceptId = indexBestPreferredByConceptId(descriptions, locales); return bestMatchByConceptId; } /** * Retrieves the fully specified name for the concept identified by the given identifier, if it exists. * <p> * The first active description with "fully specified name" as the type will be returned, where all of the following conditions apply: * <ul> * <li>a matching well-known language reference set exists for the given {@code Locale} (eg. {@code "en-GB"}); * <li>the description has a language reference set member in the reference set identified above with preferred acceptability. * </ul> * <p> * If no such description can be found, the search is repeated with the following conditions: * <ul> * <li>the description's language code matches the supplied {@code Locale}'s language (eg. {@code "en"} on description, {@code "en-US"} on {@code Locale}); * </ul> * <p> * Failing that, the whole check starts from the beginning with the next {@link Locale} in the list. * The method falls back to the first active fully specified name if the language code does not match any of the specified {@code Locale}s. * * @param conceptRef the reference to the concept for which the preferred term should be returned (may not be {@code null}) * @param locales a list of {@link Locale}s to use, in order of preference * @return the preferred term for the concept */ public SnomedDescription getFullySpecifiedName(String conceptId, List<ExtendedLocale> locales) { SnomedDescriptionSearchRequestBuilder acceptabilityReq = prepareFsnSearchByAcceptability(conceptId, locales); SnomedDescriptions preferredDescriptions = execute(acceptabilityReq); Map<String, SnomedDescription> bestPreferredByConceptId = indexBestPreferredByConceptId( preferredDescriptions, locales); if (bestPreferredByConceptId.containsKey(conceptId)) { return bestPreferredByConceptId.get(conceptId); } List<String> languageCodes = getLanguageCodes(locales); SnomedDescriptionSearchRequestBuilder languageCodeReq = prepareFsnSearchByLanguageCodes(conceptId, languageCodes); SnomedDescriptions languageCodeDescriptions = execute(languageCodeReq); Map<String, SnomedDescription> bestLanguageByConceptId = indexBestLanguageByConceptId( languageCodeDescriptions, languageCodes); if (bestLanguageByConceptId.containsKey(conceptId)) { return bestLanguageByConceptId.get(conceptId); } SnomedDescriptionSearchRequestBuilder defaultReq = prepareFsnSearchDefault(conceptId); SnomedDescriptions activeFsnDescriptions = execute(defaultReq); /* * XXX: we usually expect to see just one active FSN at this point, but depending on the given ExtendedLocale combinations, * there might be more candidates remaining. */ return Iterables.getFirst(activeFsnDescriptions, null); } public Map<String, SnomedDescription> getFullySpecifiedNames(Set<String> conceptIds, List<ExtendedLocale> locales) { if (conceptIds.isEmpty()) { return Collections.emptyMap(); } Map<String, SnomedDescription> fsnMap = newHashMap(); Set<String> conceptIdsNotInMap; SnomedDescriptionSearchRequestBuilder acceptabilityReq = prepareFsnSearchByAcceptability(conceptIds, locales); SnomedDescriptions preferredDescriptions = execute(acceptabilityReq); fsnMap.putAll(indexBestPreferredByConceptId(preferredDescriptions, locales)); conceptIdsNotInMap = Sets.difference(conceptIds, fsnMap.keySet()); if (conceptIdsNotInMap.isEmpty()) { return fsnMap; } List<String> languageCodes = getLanguageCodes(locales); SnomedDescriptionSearchRequestBuilder languageCodeReq = prepareFsnSearchByLanguageCodes(conceptIdsNotInMap, languageCodes); SnomedDescriptions languageCodeDescriptions = execute(languageCodeReq); fsnMap.putAll(indexBestLanguageByConceptId(languageCodeDescriptions, languageCodes)); conceptIdsNotInMap = Sets.difference(conceptIds, fsnMap.keySet()); if (conceptIdsNotInMap.isEmpty()) { return fsnMap; } SnomedDescriptionSearchRequestBuilder defaultReq = prepareFsnSearchDefault(conceptIdsNotInMap); SnomedDescriptions activeFsnDescriptions = execute(defaultReq); fsnMap.putAll(indexFirstByConceptId(activeFsnDescriptions)); return fsnMap; } private List<String> getLanguageCodes(List<ExtendedLocale> locales) { return FluentIterable.from(locales).transform(new Function<ExtendedLocale, String>() { @Override public String apply(ExtendedLocale input) { return input.getLanguage(); } }).toSet() // preserves iteration order, but makes elements unique .asList(); } // FSN requests private SnomedDescriptionSearchRequestBuilder prepareFsnSearchByAcceptability(String conceptId, List<ExtendedLocale> locales) { return prepareFsnSearchDefault(conceptId).filterByPreferredIn(locales); } private SnomedDescriptionSearchRequestBuilder prepareFsnSearchByAcceptability(Collection<String> conceptIds, List<ExtendedLocale> locales) { return prepareFsnSearchDefault(conceptIds).filterByPreferredIn(locales); } private SnomedDescriptionSearchRequestBuilder prepareFsnSearchByLanguageCodes(String conceptId, List<String> languageCodes) { return prepareFsnSearchDefault(conceptId).filterByLanguageCodes(languageCodes); } private SnomedDescriptionSearchRequestBuilder prepareFsnSearchByLanguageCodes(Collection<String> conceptIds, List<String> languageCodes) { return prepareFsnSearchDefault(conceptIds).filterByLanguageCodes(languageCodes); } private SnomedDescriptionSearchRequestBuilder prepareFsnSearchDefault(String conceptId) { return SnomedRequests.prepareSearchDescription().all().filterByActive(true).filterByConcept(conceptId) .filterByType(Concepts.FULLY_SPECIFIED_NAME); } private SnomedDescriptionSearchRequestBuilder prepareFsnSearchDefault(Collection<String> conceptIds) { return SnomedRequests.prepareSearchDescription().all().filterByActive(true).filterByConceptId(conceptIds) .filterByType(Concepts.FULLY_SPECIFIED_NAME); } // PT requests private SnomedDescriptionSearchRequestBuilder preparePtSearch(String conceptId, List<ExtendedLocale> locales) { return SnomedRequests.prepareSearchDescription().all().filterByActive(true).filterByConcept(conceptId) .filterByType("<<" + Concepts.SYNONYM).filterByPreferredIn(locales); } private SnomedDescriptionSearchRequestBuilder preparePtSearch(Collection<String> conceptIds, List<ExtendedLocale> locales) { return SnomedRequests.prepareSearchDescription().all().filterByActive(true).filterByConceptId(conceptIds) .filterByType("<<" + Concepts.SYNONYM).filterByPreferredIn(locales); } public static Map<String, SnomedDescription> indexBestPreferredByConceptId( Iterable<SnomedDescription> descriptions, List<ExtendedLocale> orderedLocales) { List<String> languageRefSetIds = SnomedDescriptionSearchRequestBuilder.getLanguageRefSetIds(orderedLocales); ExplicitFirstOrdering<String> languageRefSetOrdering = ExplicitFirstOrdering.create(languageRefSetIds); return extractBest(indexByConceptId(descriptions), languageRefSetIds, description -> { Set<String> preferredLanguageRefSetIds = Maps .filterValues(description.getAcceptabilityMap(), Predicates.equalTo(Acceptability.PREFERRED)) .keySet(); // the explicit first ordering will put the VIP / anticipated / first priority languages codes to the min end. return languageRefSetOrdering.min(preferredLanguageRefSetIds); }); } private Map<String, SnomedDescription> indexBestLanguageByConceptId(SnomedDescriptions descriptions, List<String> orderedLanguages) { return extractBest(indexByConceptId(descriptions), orderedLanguages, description -> description.getLanguageCode()); } private Map<String, SnomedDescription> indexFirstByConceptId(SnomedDescriptions descriptions) { return extractFirst(indexByConceptId(descriptions)); } private static Multimap<String, SnomedDescription> indexByConceptId(Iterable<SnomedDescription> descriptions) { return Multimaps.index(descriptions, description -> description.getConceptId()); } private Map<String, SnomedDescription> extractFirst( Multimap<String, SnomedDescription> descriptionsByConceptId) { Map<String, SnomedDescription> uniqueMap = Maps.transformValues(descriptionsByConceptId.asMap(), values -> Iterables.getFirst(values, null)); return ImmutableMap.copyOf(Maps.filterValues(uniqueMap, Predicates.notNull())); } private static <T> Map<String, SnomedDescription> extractBest( Multimap<String, SnomedDescription> descriptionsByConceptId, List<T> orderedValues, Function<SnomedDescription, T> predicateFactory) { Map<String, SnomedDescription> uniqueMap = Maps.transformValues(descriptionsByConceptId.asMap(), new ExtractBestFunction<T>(orderedValues, predicateFactory)); return ImmutableMap.copyOf(Maps.filterValues(uniqueMap, Predicates.notNull())); } protected abstract SnomedDescriptions execute(SnomedDescriptionSearchRequestBuilder req); }