From fec99d0791b306058a9b86faaaf315cbfcc245a4 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Wed, 22 May 2024 21:54:28 +0100 Subject: [PATCH] OpenAPI doco removed redundant status https://github.com/CatalogueOfLife/backend/issues/1302 --- ...ificationImpl.java => Classification.java} | 6 +- .../life/catalogue/matching/CleanupUtils.java | 5 +- .../life/catalogue/matching/DatasetIndex.java | 73 +++++++----- .../catalogue/matching/DatasetMapper.java | 3 - .../life/catalogue/matching/Diagnostics.java | 12 +- .../matching/HigherTaxaComparator.java | 78 +++++-------- .../java/life/catalogue/matching/IOUtils.java | 12 +- .../catalogue/matching/IndexMetadata.java | 9 ++ .../matching/IndexingApplication.java | 7 +- .../catalogue/matching/IndexingService.java | 3 +- .../catalogue/matching/MatchController.java | 109 ++++++++++++------ .../matching/MatchingApplication.java | 15 ++- .../catalogue/matching/MatchingService.java | 10 +- .../life/catalogue/matching/NameNRank.java | 25 ++-- .../catalogue/matching/NameUsageMatch.java | 16 ++- .../catalogue/matching/NameUsageMatchV1.java | 2 +- .../life/catalogue/matching/RankedName.java | 2 + .../src/main/resources/application.properties | 2 +- matching-ws/src/main/resources/logback.xml | 8 +- .../catalogue/matching/MatchingServiceIT.java | 72 ++++++------ .../catalogue/matching/NameNRankTest.java | 2 +- .../catalogue/matching/NameUsageBuilder.java | 2 +- 22 files changed, 272 insertions(+), 201 deletions(-) rename matching-ws/src/main/java/life/catalogue/matching/{LinneanClassificationImpl.java => Classification.java} (87%) diff --git a/matching-ws/src/main/java/life/catalogue/matching/LinneanClassificationImpl.java b/matching-ws/src/main/java/life/catalogue/matching/Classification.java similarity index 87% rename from matching-ws/src/main/java/life/catalogue/matching/LinneanClassificationImpl.java rename to matching-ws/src/main/java/life/catalogue/matching/Classification.java index f681f7e43..d74d23e69 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/LinneanClassificationImpl.java +++ b/matching-ws/src/main/java/life/catalogue/matching/Classification.java @@ -2,7 +2,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public class LinneanClassificationImpl implements LinneanClassification { +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "A set of higher taxa references", title = "Classification", type = "object") +public class Classification implements LinneanClassification { + String kingdom; String phylum; String clazz; diff --git a/matching-ws/src/main/java/life/catalogue/matching/CleanupUtils.java b/matching-ws/src/main/java/life/catalogue/matching/CleanupUtils.java index 3839f4d7a..0c004c9a3 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/CleanupUtils.java +++ b/matching-ws/src/main/java/life/catalogue/matching/CleanupUtils.java @@ -6,6 +6,7 @@ import java.util.regex.Pattern; public class CleanupUtils { + private static final Pattern NULL_PATTERN = Pattern.compile("^\\s*(\\\\N|\\\\?NULL|null)\\s*$"); private static final CharMatcher SPACE_MATCHER = CharMatcher.whitespace().or(CharMatcher.javaIsoControl()); @@ -26,8 +27,8 @@ public static String clean(String x) { } /** - * @param classification - * @return + * @param classification the classification object to clean + * @return a new cleaned classification object */ public static LinneanClassification clean(LinneanClassification classification) { classification.setKingdom(clean(classification.getKingdom())); diff --git a/matching-ws/src/main/java/life/catalogue/matching/DatasetIndex.java b/matching-ws/src/main/java/life/catalogue/matching/DatasetIndex.java index 5b2ea9677..ec8f0c3b5 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/DatasetIndex.java +++ b/matching-ws/src/main/java/life/catalogue/matching/DatasetIndex.java @@ -9,12 +9,13 @@ import jakarta.annotation.PostConstruct; import java.io.File; import java.io.IOException; -import java.nio.file.FileStore; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.*; import life.catalogue.api.vocab.MatchType; @@ -33,6 +34,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +/** + * Represents an index of a dataset. + */ @Service public class DatasetIndex { @@ -44,7 +48,7 @@ public class DatasetIndex { String indexPath; @Value("${working.dir}") - protected String workingDir = "/tmp/"; + String workingDir = "/tmp/"; /** Attempts to read the index from disk if it exists. */ @PostConstruct @@ -70,6 +74,11 @@ void initWithDir(Directory indexDir) { } } + /** + * Returns the metadata of the index. This includes the number of taxa, the size on disk, the + * dataset title and key, and the build information. + * @return IndexMetadata + */ public IndexMetadata getIndexMetadata(){ IndexMetadata metadata = new IndexMetadata(); @@ -77,18 +86,21 @@ public IndexMetadata getIndexMetadata(){ Path directoryPath = Path.of(indexPath); try { BasicFileAttributes attributes = Files.readAttributes(directoryPath, BasicFileAttributes.class); - FileTime creationTime = attributes.creationTime(); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); - String formattedCreationTime = dateFormat.format(creationTime.toMillis()); + Instant creationTime = attributes.creationTime().toInstant(); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + .withZone(ZoneId.systemDefault()); + String formattedCreationTime = dateFormatter.format(creationTime); metadata.setCreated(formattedCreationTime); - - FileStore fileStore = Files.getFileStore(directoryPath); - long totalSpace = fileStore.getTotalSpace(); - long usableSpace = fileStore.getUsableSpace(); - long usedSpace = totalSpace - usableSpace; - if (usedSpace > 0) { - metadata.setSizeInMB((usedSpace / 1024) / 1024); + long totalSize = 0; + try (DirectoryStream stream = Files.newDirectoryStream(directoryPath)) { + for (Path entry : stream) { + if (!Files.isDirectory(entry)) { + totalSize += Files.size(entry); + } + } } + metadata.setSizeInMB((long) (totalSize / (1024.0 * 1024.0))); + } catch (IOException e) { LOG.error("Cannot read index directory attributes", e); } @@ -118,13 +130,17 @@ public IndexMetadata getIndexMetadata(){ return metadata; } + /** + * Reads the git information from the git.json file in the working directory. + * @return Map + */ public Map readGitInfo() { ObjectMapper mapper = new ObjectMapper(); - + final String filePath = workingDir + "/git.json"; try { - if (new File(workingDir + "/git.json").exists()) { + if (new File(filePath).exists()) { // Read JSON file and parse to JsonNode - JsonNode rootNode = mapper.readTree(new File(workingDir + "/git.json")); + JsonNode rootNode = mapper.readTree(new File(filePath)); // Navigate to the author node String sha = rootNode.path("sha").asText(); @@ -140,7 +156,7 @@ public Map readGitInfo() { return Map.of("sha", sha, "url", url, "html_url", html_url, "name", name, "email", email, "date", date, "message", message); } else { - LOG.warn("Git info not found at {}", workingDir + "/dataset.json"); + LOG.warn("Git info not found at {}", filePath); } } catch (IOException e) { LOG.error("Cannot read index git information", e); @@ -148,20 +164,26 @@ public Map readGitInfo() { return Map.of(); } + /** + * Reads the dataset information from the dataset.json file in the working directory. + * @return Map + */ public Map readDatasetInfo() { ObjectMapper mapper = new ObjectMapper(); + String filePath = workingDir + "/dataset.json"; + try { - if (new File(workingDir + "/dataset.json").exists()){ - LOG.info("Loading dataset info from {}", workingDir + "/dataset.json"); + if (new File(filePath).exists()){ + LOG.info("Loading dataset info from {}", filePath); // Read JSON file and parse to JsonNode - JsonNode rootNode = mapper.readTree(new File(workingDir + "/dataset.json")); + JsonNode rootNode = mapper.readTree(new File(filePath)); // Navigate to the author node String datasetKey = rootNode.path("key").asText(); String datasetTitle = rootNode.path("title").asText(); return Map.of("datasetKey", datasetKey, "datasetTitle", datasetTitle); } else { - LOG.warn("Dataset info not found at {}", workingDir + "/dataset.json"); + LOG.warn("Dataset info not found at {}", filePath); } } catch (IOException e) { LOG.error("Cannot read index dataset information", e); @@ -228,7 +250,7 @@ public Optional getByUsageId(String usageKey) { try { TopDocs docs = getSearcher().search(query, 3); if (docs.totalHits.value > 0) { - return Optional.of(getSearcher().doc(docs.scoreDocs[0].doc)); + return Optional.of(getSearcher().storedFields().document(docs.scoreDocs[0].doc)); } else { return Optional.empty(); } @@ -334,8 +356,7 @@ private NameUsageMatch fromDoc(Document doc) { u.setSynonym(synonym); String status = doc.get(FIELD_STATUS); - u.setStatus(TaxonomicStatus.valueOf(status)); - u.getDiagnostics().setStatus(status); + u.getDiagnostics().setStatus(TaxonomicStatus.valueOf(status)); return u; } @@ -389,7 +410,7 @@ private List search(Query q, String name, boolean fuzzySearch, i TopDocs docs = searcher.search(q, maxMatches); if (docs.totalHits.value > 0) { for (ScoreDoc sdoc : docs.scoreDocs) { - NameUsageMatch match = fromDoc(searcher.doc(sdoc.doc)); + NameUsageMatch match = fromDoc(searcher.storedFields().document(sdoc.doc)); if (name.equalsIgnoreCase(match.getUsage().getCanonicalName())) { match.getDiagnostics().setMatchType(MatchType.EXACT); results.add(match); diff --git a/matching-ws/src/main/java/life/catalogue/matching/DatasetMapper.java b/matching-ws/src/main/java/life/catalogue/matching/DatasetMapper.java index 0899899b1..a1de6a3c9 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/DatasetMapper.java +++ b/matching-ws/src/main/java/life/catalogue/matching/DatasetMapper.java @@ -1,11 +1,8 @@ package life.catalogue.matching; import life.catalogue.api.vocab.DatasetOrigin; - import org.apache.ibatis.annotations.Param; - import javax.annotation.Nullable; - import java.util.List; public interface DatasetMapper { diff --git a/matching-ws/src/main/java/life/catalogue/matching/Diagnostics.java b/matching-ws/src/main/java/life/catalogue/matching/Diagnostics.java index 992409881..d46955527 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/Diagnostics.java +++ b/matching-ws/src/main/java/life/catalogue/matching/Diagnostics.java @@ -2,15 +2,25 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; + import life.catalogue.api.vocab.MatchType; +import life.catalogue.api.vocab.TaxonomicStatus; + import lombok.Data; @JsonInclude(JsonInclude.Include.NON_NULL) @Data +@Schema(description = "Diagnostics for a name match including the type of match and confidence level", title = "Diagnostics", type = "object") public class Diagnostics { + @Schema(description = "The match type, e.g. 'exact', 'fuzzy', 'partial', 'none'") MatchType matchType; @JsonIgnore MatchIssueType matchIssueType; + @Schema(description = "Confidence level in percent") Integer confidence; - String status; + @Schema(description = "The status of the match e.g. ACCEPTED, SYNONYM, AMBIGUOUS, EXCLUDED, etc.") + TaxonomicStatus status; + @Schema(description = "Additional notes about the match") String note; } diff --git a/matching-ws/src/main/java/life/catalogue/matching/HigherTaxaComparator.java b/matching-ws/src/main/java/life/catalogue/matching/HigherTaxaComparator.java index 48b40e0a9..d8b6d8f7a 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/HigherTaxaComparator.java +++ b/matching-ws/src/main/java/life/catalogue/matching/HigherTaxaComparator.java @@ -1,7 +1,5 @@ package life.catalogue.matching; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.io.Closeables; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -28,7 +26,7 @@ public class HigherTaxaComparator { Rank.FAMILY, "family.txt"); private static final Set NON_NAMES = new HashSet<>(); - private final Map> syn = new HashMap<>(); + private final Map> synonyms = new HashMap<>(); private final Map kingdoms = Arrays.stream(Kingdom.values()) .collect(Collectors.toMap(k -> norm(k.name()), Function.identity())); @@ -87,26 +85,6 @@ public boolean isInKingdoms(LinneanClassification n, Kingdom... kingdoms) { return false; } - /** - * Lookup higher taxa synonym dictionary across all ranks and return the first match found - * - * @param higherTaxon - * @return the looked up accepted name or the original higherTaxon - */ - @VisibleForTesting - protected String lookup(String higherTaxon) { - if (higherTaxon == null) { - return null; - } - for (Rank r : syn.keySet()) { - String result = lookup(higherTaxon, r); - if (result != null) { - return result; - } - } - return higherTaxon; - } - /** * Lookup synonym for given higher rank. Can be null. * @@ -122,11 +100,11 @@ public String lookup(String higherTaxon, Rank rank) { if (isBlacklisted(higherTaxon)) { return null; } - if (syn.containsKey(rank)) { + if (synonyms.containsKey(rank)) { String normedHT = norm(higherTaxon); - Map x = syn.get(rank); - if (syn.get(rank).containsKey(normedHT)) { - return syn.get(rank).get(normedHT); + Map x = synonyms.get(rank); + if (synonyms.get(rank).containsKey(normedHT)) { + return synonyms.get(rank).get(normedHT); } } return higherTaxon; @@ -134,20 +112,18 @@ public String lookup(String higherTaxon, Rank rank) { /** * Check for obvious, blacklisted garbage and return true if thats the case. The underlying set is - * hosted at http://rs.gbif.org/dictionaries/authority/blacklisted.txt + * hosted at blacklisted.txt */ public boolean isBlacklisted(String name) { if (name != null) { name = norm(name); - if (NON_NAMES.contains(name)) { - return true; - } + return NON_NAMES.contains(name); } return false; } /** - * @return non empty uppercased string with normalized whitespace and all non latin letters + * @return non-empty uppercased string with normalized whitespace and all non latin letters * replaced. Or null */ protected static String norm(String x) { @@ -162,13 +138,13 @@ protected static String norm(String x) { /** * @param file the synonym file on rs.gbif.org - * @return + * @return a map of synonyms */ private Map readSynonymUrl(Rank rank, String file) { URL url = RsGbifOrg.synonymUrl(file); LOG.info("Reading synonyms from " + url.toString()); try (InputStream synIn = url.openStream()) { - return IOUtils.streamToMap(synIn, 0, 1, true); + return IOUtils.streamToMap(synIn); } catch (IOException e) { LOG.warn("Cannot read synonym map from stream for {}. Use empty map instead.", rank, e); } @@ -178,7 +154,7 @@ private Map readSynonymUrl(Rank rank, String file) { private Map readSynonymStream(Rank rank, String filePath) { ClassLoader classLoader = getClass().getClassLoader(); try (InputStream synIn = classLoader.getResourceAsStream(filePath)) { - return IOUtils.streamToMap(synIn, 0, 1, true); + return IOUtils.streamToMap(synIn); } catch (IOException e) { LOG.warn("Cannot read synonym map from stream for {}. Use empty map instead.", rank, e); } @@ -187,10 +163,10 @@ private Map readSynonymStream(Rank rank, String filePath) { /** Reads blacklisted names from rs.gbif.org */ private void readOnlineBlacklist() { - try { - URL url = RsGbifOrg.authorityUrl(RsGbifOrg.FILENAME_BLACKLIST); - LOG.debug("Reading " + url.toString()); - readBlacklistStream(url.openStream()); + URL url = RsGbifOrg.authorityUrl(RsGbifOrg.FILENAME_BLACKLIST); + try (InputStream in = url.openStream()) { + LOG.debug("Reading {}", url); + readBlacklistStream(in); } catch (IOException e) { LOG.warn("Cannot read online blacklist.", e); } @@ -203,10 +179,8 @@ private void readBlacklistStream(InputStream in) { NON_NAMES.addAll(IOUtils.streamToSet(in)); } catch (IOException e) { LOG.warn("Cannot read blacklist. Use empty set instead.", e); - } finally { - Closeables.closeQuietly(in); } - LOG.debug("loaded " + NON_NAMES.size() + " blacklisted names"); + LOG.debug("loaded {} blacklisted names", NON_NAMES.size()); } /** @@ -225,7 +199,7 @@ public void loadClasspathDicts(String classpathFolder) throws IOException { Map synonyms = readSynonymStream(rank, filePath); setSynonyms(rank, synonyms); } else { - LOG.error("Unable to find resource: {}", filePath); + LOG.error("Unable to find synonym file: {}", filePath); } } } @@ -236,7 +210,7 @@ public void loadClasspathDicts(String classpathFolder) throws IOException { if (blackIn != null) { readBlacklistStream(blackIn); } else { - LOG.error("Unable to find resource: {}", blacklistFilePath); + LOG.error("Unable to find blacklist file: {}", blacklistFilePath); } } } @@ -256,10 +230,10 @@ public void loadOnlineDicts() { /** * Sets the synonym lookup map for a given rank. Names will be normalised and checked for - * existance of the same entry as key or value. + * existence of the same entry as key or value. * - * @param rank - * @param synonyms + * @param rank the rank to set synonyms for + * @param synonyms the synonym map to set */ public void setSynonyms(Rank rank, Map synonyms) { Map synonymsNormed = new HashMap<>(); @@ -278,12 +252,12 @@ public void setSynonyms(Rank rank, Map synonyms) { } } - syn.put(rank, synonymsNormed); + this.synonyms.put(rank, synonymsNormed); LOG.debug("Loaded " + synonyms.size() + " " + rank.name() + " synonyms "); // also insert kingdom enum lookup in case of kingdom synonyms if (Rank.KINGDOM == rank) { - Map map = syn.get(Rank.KINGDOM); + Map map = this.synonyms.get(Rank.KINGDOM); if (map != null) { for (String syn : map.keySet()) { Kingdom k = null; @@ -308,7 +282,7 @@ public void setSynonyms(Rank rank, Map synonyms) { /** @return the number of entries across all ranks */ public int size() { int all = 0; - for (Rank r : syn.keySet()) { + for (Rank r : synonyms.keySet()) { all += size(r); } return all; @@ -316,8 +290,8 @@ public int size() { /** @return the number of entries for a given rank */ public int size(Rank rank) { - if (syn.containsKey(rank)) { - return syn.get(rank).size(); + if (synonyms.containsKey(rank)) { + return synonyms.get(rank).size(); } return 0; } diff --git a/matching-ws/src/main/java/life/catalogue/matching/IOUtils.java b/matching-ws/src/main/java/life/catalogue/matching/IOUtils.java index 467dd394e..50642eea0 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/IOUtils.java +++ b/matching-ws/src/main/java/life/catalogue/matching/IOUtils.java @@ -32,22 +32,16 @@ static InputStream classpathStream(String path) { return in; } - static Map streamToMap( - InputStream source, int key, int value, boolean trimToNull) { + static Map streamToMap(InputStream source) { LineIterator lines = getLineIterator(source); Map result = new HashMap<>(); - int maxCols = key > value ? key : value + 1; while (lines.hasNext()) { String line = lines.nextLine(); // ignore comments if (!ignore(line)) { String[] parts = TAB_DELIMITED.split(line); - if (maxCols <= parts.length) { - if (trimToNull) { - result.put(StringUtils.trimToNull(parts[key]), StringUtils.trimToNull(parts[value])); - } else { - result.put(parts[key], parts[value]); - } + if (parts.length >= 2) { + result.put(StringUtils.trimToNull(parts[0]), StringUtils.trimToNull(parts[1])); } } } diff --git a/matching-ws/src/main/java/life/catalogue/matching/IndexMetadata.java b/matching-ws/src/main/java/life/catalogue/matching/IndexMetadata.java index efab2a0bb..20873a431 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/IndexMetadata.java +++ b/matching-ws/src/main/java/life/catalogue/matching/IndexMetadata.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.HashMap; @@ -12,12 +13,20 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) @Data +@Schema(description = "Metadata about the index and software used to access the index through webservices", title = "IndexMetadata", type = "object") public class IndexMetadata { + @Schema(description = "The dataset key") String datasetKey; + @Schema(description = "The dataset description") String datasetTitle; + @Schema(description = "When the index was created") String created; + @Schema(description = "The size of the index in MB") Long sizeInMB = 0L; + @Schema(description = "The number of name usages in the index") Long taxonCount = 0L; + @Schema(description = "Counts of taxa by rank") Map taxaByRankCount = new HashMap<>(); + @Schema(description = "Git build information") Map buildInfo = new HashMap<>(); } diff --git a/matching-ws/src/main/java/life/catalogue/matching/IndexingApplication.java b/matching-ws/src/main/java/life/catalogue/matching/IndexingApplication.java index f28506f08..b97bf2a97 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/IndexingApplication.java +++ b/matching-ws/src/main/java/life/catalogue/matching/IndexingApplication.java @@ -1,6 +1,5 @@ package life.catalogue.matching; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -13,7 +12,11 @@ @Profile("indexing") public class IndexingApplication implements ApplicationRunner { - @Autowired IndexingService indexingService; + final IndexingService indexingService; + + public IndexingApplication(IndexingService indexingService) { + this.indexingService = indexingService; + } @Override public void run(ApplicationArguments args) throws Exception { diff --git a/matching-ws/src/main/java/life/catalogue/matching/IndexingService.java b/matching-ws/src/main/java/life/catalogue/matching/IndexingService.java index ed0097797..bf08637c3 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/IndexingService.java +++ b/matching-ws/src/main/java/life/catalogue/matching/IndexingService.java @@ -156,7 +156,7 @@ public void writeCLBToFile(@NotNull final String datasetKeyInput) throws Excepti factory.getConfiguration().addMapper(IndexingMapper.class); factory.getConfiguration().addMapper(DatasetMapper.class); - // FIXME - resolve the magic keys... + // resolve the magic keys... Optional datasetKey = Optional.empty(); try { datasetKey = Optional.of(Integer.parseInt(datasetKeyInput)); @@ -263,7 +263,6 @@ public void indexFile(String exportPath, String indexPath) throws Exception { } if (counter.get() % 100000 == 0) { LOG.info("Indexed: {} taxa", counter.get()); - indexWriter.commit(); } NameUsage nameUsage = diff --git a/matching-ws/src/main/java/life/catalogue/matching/MatchController.java b/matching-ws/src/main/java/life/catalogue/matching/MatchController.java index 26a2722f8..0c393efe8 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/MatchController.java +++ b/matching-ws/src/main/java/life/catalogue/matching/MatchController.java @@ -30,11 +30,8 @@ import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.WebRequest; /** @@ -53,21 +50,23 @@ public class MatchController implements ErrorController { private static final String PATH = "/error"; @Hidden - @RequestMapping(value = PATH, produces = "application/json") - public Map error(WebRequest aRequest){ - Map body = getErrorAttributes(aRequest); - String traceRequested = aRequest.getParameter("trace"); - if (StringUtils.isNotBlank(traceRequested) && - (traceRequested.equalsIgnoreCase("true") || traceRequested.equalsIgnoreCase("on"))) { - String trace = (String) body.get("trace"); - if (trace != null){ - String[] lines = trace.split("\n\t"); - body.put("trace", lines); - } + @GetMapping(value = PATH, produces = "application/json") + public Map error(WebRequest request) { + Map errorAttributes = getErrorAttributes(request); + String traceRequested = request.getParameter("trace"); + if (isTraceRequested(traceRequested)) { + Optional.ofNullable(errorAttributes.get("trace")) + .map(Object::toString) + .ifPresent(trace -> errorAttributes.put("trace", trace.split("\n\t"))); } else { - body.remove("trace"); + errorAttributes.remove("trace"); } - return body; + return errorAttributes; + } + + private boolean isTraceRequested(String traceRequested) { + return StringUtils.isNotBlank(traceRequested) && + (traceRequested.equalsIgnoreCase("true") || traceRequested.equalsIgnoreCase("on")); } private boolean getTraceParameter(HttpServletRequest request) { @@ -75,7 +74,7 @@ private boolean getTraceParameter(HttpServletRequest request) { if (parameter == null) { return false; } - return !"false".equals(parameter.toLowerCase()); + return !"false".equalsIgnoreCase(parameter); } @Autowired @@ -115,10 +114,42 @@ private Map getErrorAttributes(WebRequest request) { value = {"v2/metadata"}, produces = "application/json") public IndexMetadata metadata(){ - return matchingService.getIndexMetadata(); } + @Hidden + @GetMapping( + value = {"species/match", "match"}, + produces = "application/json") + public NameUsageMatch matchOldPaths( + @RequestParam(value = "usageKey", required = false) String usageKey, + @RequestParam(value = "name", required = false) String scientificName2, + @RequestParam(value = "scientificName", required = false) String scientificName, + @RequestParam(value = "authorship", required = false) String authorship2, + @RequestParam(value = "scientificNameAuthorship", required = false) String authorship, + @RequestParam(value = "rank", required = false) String rank2, + @RequestParam(value = "taxonRank", required = false) String rank, + @RequestParam(value = "genericName", required = false) String genericName, + @RequestParam(value = "specificEpithet", required = false) String specificEpithet, + @RequestParam(value = "infraspecificEpithet", required = false) String infraspecificEpithet, + Classification classification, + @RequestParam(value = "strict", required = false) Boolean strict, + @RequestParam(value = "verbose", required = false) Boolean verbose, + HttpServletRequest response) { + return matchV2( + usageKey, + scientificName2, scientificName, + authorship, authorship2, + removeNulls(genericName), + removeNulls(specificEpithet), + removeNulls(infraspecificEpithet), + rank, + rank2, + classification, + bool(strict), + bool(verbose), + response); + } @Operation( operationId = "matchNames", @@ -150,20 +181,20 @@ public IndexMetadata metadata(){ description = "Filters by taxonomic rank.", schema = @Schema(implementation = Rank.class)), @Parameter(name = "taxonRank", hidden = true), - @Parameter(name = "kingdom", description = "Kingdom to match.", in = ParameterIn.QUERY), - @Parameter(name = "phylum", description = "Phylum to match.", in = ParameterIn.QUERY), - @Parameter(name = "order", description = "Order to match.", in = ParameterIn.QUERY), - @Parameter(name = "class", description = "Class to match.", in = ParameterIn.QUERY), - @Parameter(name = "family", description = "Family to match.", in = ParameterIn.QUERY), - @Parameter(name = "genus", description = "Genus to match.", in = ParameterIn.QUERY), - @Parameter(name = "subgenus", description = "Subgenus to match.", in = ParameterIn.QUERY), - @Parameter(name = "species", description = "Species to match.", in = ParameterIn.QUERY), + @Parameter(name = "kingdom", description = "Kingdom to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "phylum", description = "Phylum to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "order", description = "Order to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "class", description = "Class to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "family", description = "Family to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "genus", description = "Genus to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "subgenus", description = "Subgenus to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), + @Parameter(name = "species", description = "Species to match.", in = ParameterIn.QUERY, schema = @Schema(implementation = String.class)), @Parameter( name = "genericName", description = "Generic part of the name to match when given as atomised parts instead of the full name parameter."), - @Parameter(name = "specificEpithet", description = "Specific epithet to match."), - @Parameter(name = "infraspecificEpithet", description = "Infraspecific epithet to match."), + @Parameter(name = "specificEpithet", description = "Specific epithet to match.", schema = @Schema(implementation = String.class)), + @Parameter(name = "infraspecificEpithet", description = "Infraspecific epithet to match.", schema = @Schema(implementation = String.class)), @Parameter(name = "classification", hidden = true), @Parameter(name = "arg10", hidden = true), @Parameter( @@ -193,7 +224,7 @@ public NameUsageMatch matchV2( @RequestParam(value = "genericName", required = false) String genericName, @RequestParam(value = "specificEpithet", required = false) String specificEpithet, @RequestParam(value = "infraspecificEpithet", required = false) String infraspecificEpithet, - LinneanClassificationImpl classification, + Classification classification, @RequestParam(value = "strict", required = false) Boolean strict, @RequestParam(value = "verbose", required = false) Boolean verbose, HttpServletRequest response) { @@ -245,32 +276,38 @@ public NameUsageMatch matchV2( @Parameter( name = "kingdom", description = "Kingdom to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "phylum", description = "Phylum to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "order", description = "Order to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "class", description = "Class to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "family", description = "Family to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "genus", description = "Genus to match.", - in = ParameterIn.QUERY + in = ParameterIn.QUERY, + schema = @Schema(implementation = String.class) ), @Parameter( name = "genericName", @@ -314,7 +351,7 @@ public Object matchV1( @RequestParam(value = "genericName", required = false) String genericName, @RequestParam(value = "specificEpithet", required = false) String specificEpithet, @RequestParam(value = "infraspecificEpithet", required = false) String infraspecificEpithet, - LinneanClassificationImpl classification, + Classification classification, @RequestParam(value = "strict", required = false) Boolean strict, @RequestParam(value = "verbose", required = false) Boolean verbose, HttpServletRequest response) { diff --git a/matching-ws/src/main/java/life/catalogue/matching/MatchingApplication.java b/matching-ws/src/main/java/life/catalogue/matching/MatchingApplication.java index 33ff8d118..5dc67cd38 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/MatchingApplication.java +++ b/matching-ws/src/main/java/life/catalogue/matching/MatchingApplication.java @@ -8,7 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -26,17 +25,25 @@ @Profile("web") public class MatchingApplication implements ApplicationRunner { - private static final Logger LOG = LoggerFactory.getLogger(MatchingService.class); - @Autowired protected MatchingService matchingService; + private static final Logger LOG = LoggerFactory.getLogger(MatchingApplication.class); + + protected final MatchingService matchingService; @Value("${version}") String version; @Value("${licence}") String licence; @Value("${licence.url}") String licenceUrl; + public MatchingApplication(MatchingService matchingService) { + this.matchingService = matchingService; + } + @Override public void run(ApplicationArguments args) { IndexMetadata metadata = matchingService.getIndexMetadata(); - LOG.info("Web services started. Index size: {} taxa", NumberFormat.getInstance().format(metadata.getTaxonCount())); + LOG.info("Web services started. Index size: {} taxa, size on disk: {}", + NumberFormat.getInstance().format(metadata.getTaxonCount()), + metadata.getSizeInMB() > 0 ? NumberFormat.getInstance().format(metadata.getSizeInMB()) + "MB" : "unknown" + ); } @Bean diff --git a/matching-ws/src/main/java/life/catalogue/matching/MatchingService.java b/matching-ws/src/main/java/life/catalogue/matching/MatchingService.java index 780af0e7b..b91cfce16 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/MatchingService.java +++ b/matching-ws/src/main/java/life/catalogue/matching/MatchingService.java @@ -272,7 +272,7 @@ public NameUsageMatch match( NameUsageMatch match; // When provided a usageKey is used exclusively - if (usageKey != null) { + if (StringUtils.isNotBlank(usageKey)) { match = datasetIndex.matchByUsageKey(usageKey); // maintain backward compatible API @@ -334,7 +334,7 @@ private NameUsageMatch matchInternal( // clean strings, replacing odd whitespace, iso controls and trimming scientificName = CleanupUtils.clean(scientificName); if (classification == null) { - classification = new LinneanClassificationImpl(); + classification = new Classification(); } else { cleanClassification(classification); } @@ -630,7 +630,7 @@ private List queryFuzzy( // -10 - +5 final int rankSimilarity = rankSimilarity(rank, m.getUsage().getRank()); // -5 - +1 - final int statusScore = STATUS_SCORE.get(m.getStatus()); + final int statusScore = STATUS_SCORE.get(m.getDiagnostics().getStatus()); // -25 - 0 final int fuzzyMatchUnlikely = fuzzyMatchUnlikelyhood(canonicalName, m); @@ -671,7 +671,7 @@ private List queryHigher( // -10 - +5 final int rankSimilarity = rankSimilarity(rank, m.getUsage().getRank()) * 2; // -5 - +1 - final int statusScore = STATUS_SCORE.get(m.getStatus()); + final int statusScore = STATUS_SCORE.get(m.getDiagnostics().getStatus()); // preliminary total score, -5 - 20 distance to next best match coming below! m.getDiagnostics() @@ -711,7 +711,7 @@ private List queryStrict( // -10 - +5 final int rankSimilarity = incNegScore(rankSimilarity(rank, m.getUsage().getRank()), 10); // -5 - +1 - final int statusScore = STATUS_SCORE.get(m.getStatus()); + final int statusScore = STATUS_SCORE.get(m.getDiagnostics().getStatus()); // preliminary total score, -5 - 20 distance to next best match coming below! m.getDiagnostics() diff --git a/matching-ws/src/main/java/life/catalogue/matching/NameNRank.java b/matching-ws/src/main/java/life/catalogue/matching/NameNRank.java index e132dbf21..cd9247e83 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/NameNRank.java +++ b/matching-ws/src/main/java/life/catalogue/matching/NameNRank.java @@ -37,13 +37,13 @@ public NameNRank(String name, Rank rank) { /** * Construct the best possible full name with authorship and rank out of various input parameters * - * @param scientificName - * @param authorship - * @param specificEpithet - * @param infraSpecificEpithet - * @param rank - * @param classification - * @return + * @param scientificName the full scientific name + * @param authorship the authorship + * @param specificEpithet the species epithet + * @param infraSpecificEpithet the infraspecific epithet + * @param rank the rank + * @param classification the classification + * @return a new NameNRank instance */ public static NameNRank build( @Nullable String scientificName, @@ -55,7 +55,7 @@ public static NameNRank build( @Nullable LinneanClassification classification) { // make sure we have a classification instance - classification = classification == null ? new LinneanClassificationImpl() : classification; + classification = classification == null ? new Classification() : classification; final String genus = clean(first(genericName, classification.getGenus())); // If given primarily trust the scientific name, especially since these can be unparsable names // like OTUs @@ -80,7 +80,7 @@ && useScientificName( Rank clRank = lowestRank(classification); if (genus == null && (clRank == null || clRank.isSuprageneric())) { // use epithets if existing - otherwise higher rank if given - if (any(specificEpithet, infraSpecificEpithet, authorship)) { + if (anyNonEmpty(specificEpithet, infraSpecificEpithet, authorship)) { // we dont have any genus or species binomen given, just epithets :( StringBuilder sb = new StringBuilder(); sb.append("?"); // no genus @@ -104,7 +104,6 @@ && useScientificName( pn.setInfraspecificEpithet(clean(infraSpecificEpithet)); pn.setRank(rank); Authorship authorship1 = Authorship.authors(clean(authorship)); - // FIXME is this ok ? pn.setCombinationAuthorship(authorship1); // see if species rank in classification can contribute sth if (exists(classification.getSpecies())) { @@ -162,7 +161,7 @@ private static String clean(String x) { return x; } - private static boolean any(String... x) { + private static boolean anyNonEmpty(String... x) { if (x != null) { for (String y : x) { if (exists(y)) return true; @@ -209,8 +208,8 @@ private static void warnIfMissing(String name, @Nullable String epithet, String } } - private static boolean exists(@Nullable String x) { - return !StringUtils.isBlank(x); + private static boolean exists(String x) { + return StringUtils.isNotBlank(x); } @VisibleForTesting diff --git a/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatch.java b/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatch.java index d9e817090..2e567c711 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatch.java +++ b/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatch.java @@ -5,6 +5,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import io.swagger.v3.oas.annotations.media.Schema; + import life.catalogue.api.vocab.TaxonomicStatus; import lombok.Data; import org.gbif.nameparser.api.Rank; @@ -15,21 +17,27 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) @Data +@Schema(description = "A name usage match returned by the webservices. Includes higher taxonomy and diagnostics", title = "NameUsageMatch", type = "object") public class NameUsageMatch implements LinneanClassification { + @Schema(description = "If the matched usage is a synonym") boolean synonym = false; + @Schema(description = "The matched name usage") RankedName usage; - @JsonIgnore TaxonomicStatus status; + @Schema(description = "The accepted name usage for the match. This will only be populated when we've matched a synonym name usage.") RankedName acceptedUsage; + @Schema(description = "The classification of the accepted name usage. ") List classification = new ArrayList<>(); + @Schema(description = "A list of similar matches with lower confidence scores ") List alternatives = new ArrayList<>(); + @Schema(description = "Diagnostics for a name match including the type of match and confidence level") Diagnostics diagnostics = new Diagnostics(); private String nameFor(Rank rank) { return getClassification().stream() .filter(c -> c.getRank().equals(rank)) .findFirst() - .map(c -> c.getName()) + .map(RankedName::getName) .orElse(null); } @@ -37,7 +45,7 @@ private String keyFor(Rank rank) { return getClassification().stream() .filter(c -> c.getRank().equals(rank)) .findFirst() - .map(c -> c.getKey()) + .map(RankedName::getKey) .orElse(null); } @@ -73,7 +81,7 @@ public String getHigherRankKey(Rank rank) { return this.getClassification().stream() .filter(c -> c.getRank().equals(rank)) .findFirst() - .map(c -> c.getKey()) + .map(RankedName::getKey) .orElse(null); } diff --git a/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatchV1.java b/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatchV1.java index ca4dec226..c371b2cce 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatchV1.java +++ b/matching-ws/src/main/java/life/catalogue/matching/NameUsageMatchV1.java @@ -74,7 +74,7 @@ public static Optional createFrom(NameUsageMatch nameUsageMatc if (nameUsageMatch.getAcceptedUsage() != null) match.setAcceptedUsageKey(Integer.parseInt(nameUsageMatch.getAcceptedUsage().getKey())); - if (nameUsageMatch.getStatus() != null) match.setStatus(nameUsageMatch.getStatus().name()); + if (nameUsageMatch.getDiagnostics().getStatus() != null) match.setStatus(nameUsageMatch.getDiagnostics().getStatus().name()); match.setConfidence(nameUsageMatch.getDiagnostics().getConfidence()); match.setNote(nameUsageMatch.getDiagnostics().getNote()); match.setMatchType(nameUsageMatch.getDiagnostics().getMatchType()); diff --git a/matching-ws/src/main/java/life/catalogue/matching/RankedName.java b/matching-ws/src/main/java/life/catalogue/matching/RankedName.java index 11085d716..3881a97dd 100644 --- a/matching-ws/src/main/java/life/catalogue/matching/RankedName.java +++ b/matching-ws/src/main/java/life/catalogue/matching/RankedName.java @@ -2,8 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Objects; +import io.swagger.v3.oas.annotations.media.Schema; import org.gbif.nameparser.api.Rank; +@Schema(description = "A name with an identifier and a taxonomic rank", title = "RankedName", type = "object") public class RankedName { private String key; private String name; diff --git a/matching-ws/src/main/resources/application.properties b/matching-ws/src/main/resources/application.properties index c41ad4f94..b7333620a 100644 --- a/matching-ws/src/main/resources/application.properties +++ b/matching-ws/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.application.name=@project.artifactId@ +spring.application.name=matching-ws version=@project.version@ mybatis.config-location=mybatis-config.xml clb.url=jdbc:postgresql://localhost:5432/clb diff --git a/matching-ws/src/main/resources/logback.xml b/matching-ws/src/main/resources/logback.xml index 97146cb0d..5dd4bcf68 100644 --- a/matching-ws/src/main/resources/logback.xml +++ b/matching-ws/src/main/resources/logback.xml @@ -1,7 +1,13 @@ + + + + %highlight(%clr([${LOG_LEVEL_PATTERN:-%4p}]) %clr([%logger{0}]){magenta} %msg%n) + + - diff --git a/matching-ws/src/test/java/life/catalogue/matching/MatchingServiceIT.java b/matching-ws/src/test/java/life/catalogue/matching/MatchingServiceIT.java index 93ceedec0..e4d5604d9 100644 --- a/matching-ws/src/test/java/life/catalogue/matching/MatchingServiceIT.java +++ b/matching-ws/src/test/java/life/catalogue/matching/MatchingServiceIT.java @@ -356,7 +356,7 @@ public void testBadPlantKingdom() throws IOException { @Test public void testHomonyms() { // Oenanthe - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertNoMatch("Oenanthe", cl); cl.setKingdom("Animalia"); @@ -367,7 +367,7 @@ public void testHomonyms() { assertMatch("Oenante", cl, 3034893, new IntRange(84, 95)); // Acanthophora - cl = new LinneanClassificationImpl(); + cl = new Classification(); assertNoMatch("Acanthophora", cl); // there are 3 genera in animalia, 2 synonyms and 1 accepted but they differ at phylum level @@ -418,7 +418,7 @@ public void testHomonyms() { assertMatch("Pima concolor", cl, 3925, new IntRange(90, 100)); // Amphibia is a homonym genus, but also and most prominently a class! - cl = new LinneanClassificationImpl(); + cl = new Classification(); // non existing "species" name. Amphibia could either be the genus or the class. As we assertNoMatch("Amphibia eyecount", cl); @@ -437,7 +437,7 @@ public void testHomonyms() { public void testHomonyms2() throws IOException { // this hits 2 species synonyms with no such name being accepted // nub match must pick one if the accepted name of all synonyms is the same! - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Plantae"); cl.setFamily("Poaceae"); assertMatch("Elytrigia repens", cl, 7522774, new IntRange(92, 100)); @@ -453,7 +453,7 @@ public void testHomonyms2() throws IOException { */ @Test public void testHomonyms3() throws IOException { - assertMatch("Siphonophora", new LinneanClassificationImpl(), 1, new IntRange(90, 100)); + assertMatch("Siphonophora", new Classification(), 1, new IntRange(90, 100)); } @Test @@ -560,7 +560,7 @@ public void testAuthorshipMatching() throws IOException { */ @Test public void testAuthorshipMatchingGIASIP() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("Bromus sterilis", cl, 8341523, new IntRange(95, 99)); assertMatch("Bromus sterilis Guss.", cl, 8341523, new IntRange(99, 100)); assertMatch("Bromus sterilis Gus", cl, 8341523, new IntRange(98, 100)); @@ -583,13 +583,13 @@ public void testAuthorshipMatchingGIASIP() throws IOException { cl.setKingdom("Plantae"); cl.setFamily("Oxalidaceae"); assertMatch("Daphnia Korth", cl, 3626852, new IntRange(96, 100)); - cl = new LinneanClassificationImpl(); + cl = new Classification(); assertMatch("Daphnia Rafinesque", cl, 7956551, new IntRange(88, 94)); cl.setKingdom("Animalia"); cl.setFamily("Calanidae"); assertMatch("Daphnia Rafinesque", cl, 4333792, new IntRange(98, 100)); - cl = new LinneanClassificationImpl(); + cl = new Classification(); assertMatch("Carpobrotus edulis", cl, 3084842, new IntRange(95, 99)); assertMatch("Carpobrotus edulis N. E. Br.", cl, 3084842, new IntRange(98, 100)); @@ -604,7 +604,7 @@ public void testAuthorshipMatchingGIASIP() throws IOException { assertMatch("Carpobrotus edulis L.Bolus", cl, 7475472, new IntRange(95, 100)); assertMatch("Carpobrotus edulis Bolus", cl, 7475472, new IntRange(95, 100)); assertMatch("Carpobrotus dulcis Bolus", cl, 3703510, new IntRange(95, 100)); - cl = new LinneanClassificationImpl(); + cl = new Classification(); assertMatch("Celastrus orbiculatus", cl, 8104460, new IntRange(95, 99)); assertMatch("Celastrus orbiculatus Murray", cl, 8104460, new IntRange(98, 100)); @@ -633,7 +633,7 @@ public void testAuthorshipMatchingGIASIP() throws IOException { @Test public void testOtuMatching() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("SH205817.07FU", cl, 9732858, new IntRange(95, 100)); @@ -666,7 +666,7 @@ public void testOtuMatching() throws IOException { */ @Test public void testViruses() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("Inachis io", cl, 5881450, new IntRange(92, 100)); assertMatch("Inachis io (Linnaeus)", cl, 5881450, new IntRange(95, 100)); assertMatch("Inachis io NPV", cl, 8562651, new IntRange(95, 100)); @@ -685,7 +685,7 @@ public void testViruses() throws IOException { */ @Test public void testPOR2704() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Plantae"); cl.setFamily("Scrophulariaceae"); // nowadays Plantaginaceae as in our nub/col assertMatch("Linaria pedunculata (L.) Chaz.", cl, 3172168, new IntRange(90, 100)); @@ -694,7 +694,7 @@ public void testPOR2704() throws IOException { /** Classification names need to be parsed if they are no monomials already */ @Test public void testClassificationWithAuthors() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Fungi Bartling, 1830"); cl.setPhylum("Ascomycota Caval.-Sm., 1998"); cl.setClazz("Lecanoromycetes, O.E. Erikss. & Winka, 1997"); @@ -707,7 +707,7 @@ public void testClassificationWithAuthors() throws IOException { /** Non existing species should match genus Quedius http://dev.gbif.org/issues/browse/POR-1712 */ @Test public void testPOR1712() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setClazz("Hexapoda"); cl.setFamily("Staphylinidae"); cl.setGenus("Quedius"); @@ -722,7 +722,7 @@ public void testPOR1712() throws IOException { */ @Test public void testPOR2701() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setPhylum("Chordata"); cl.setClazz("Mammalia"); cl.setOrder("Carnivora"); @@ -740,7 +740,7 @@ public void testPOR2701() throws IOException { */ @Test public void testPOR2684() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Plantae"); cl.setFamily("Labiatae"); cl.setGenus("Brunella"); @@ -754,7 +754,7 @@ public void testPOR2684() throws IOException { */ @Test public void testPOR2469() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setPhylum("Arthropoda"); cl.setClazz("Insecta"); @@ -771,7 +771,7 @@ public void testPOR2469() throws IOException { */ @Test public void testFeedback1379() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setFamily("Helicidae"); assertMatch("iberus gualtieranus", cl, 4564258, new IntRange(95, 100)); assertMatch("Iberus gualterianus", cl, 4564258, new IntRange(98, 100)); @@ -785,7 +785,7 @@ public void testFeedback1379() throws IOException { */ @Test public void testPOR2607() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setFamily("Chrysomelidae"); assertMatch("Oreina", cl, 6757727, new IntRange(95, 100)); @@ -806,7 +806,7 @@ public void testPOR2607() throws IOException { */ @Test public void testIndet() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); // assertMatch("Peperomia induta", cl, 4189260, new IntRange(95, 100)); // assertMatch("Peperomia indet", cl, 3086367, MatchType.HIGHERRANK); assertMatch("Lacerta bilineata indet", cl, 6159243, new IntRange(95, 100)); @@ -821,13 +821,13 @@ public void testBlogNames() throws IOException { assertMatch("Xysticus spec.", cl, 2164999, MatchType.HIGHERRANK); // http://www.gbif.org/occurrence/1061576151/verbatim - cl = new LinneanClassificationImpl(); + cl = new Classification(); cl.setFamily("Poaceae"); cl.setGenus("Triodia"); assertMatch("Triodia sp.", cl, 2702695); // http://www.gbif.org/occurrence/1037140379/verbatim - cl = new LinneanClassificationImpl(); + cl = new Classification(); cl.setKingdom("Plantae"); cl.setFamily("XYRIDACEAE"); cl.setGenus("Xyris"); @@ -837,7 +837,7 @@ public void testBlogNames() throws IOException { assertMatch("Xyris kralii Wand.", cl, 2692599, MatchType.HIGHERRANK); // http://www.gbif.org/occurrence/144904719/verbatim - cl = new LinneanClassificationImpl(); + cl = new Classification(); cl.setKingdom("Plantae"); cl.setFamily("GRAMINEAE"); assertMatch( @@ -850,7 +850,7 @@ public void testBlogNames() throws IOException { @Test public void testImprovedMatching() throws IOException { // http://www.gbif.org/occurrence/164267402/verbatim - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("Zabidius novemaculeatus", cl, 2394331, new IntRange(98, 100)); assertMatch("Zabideus novemaculeatus", cl, 2394331, new IntRange(75, 85)); @@ -875,7 +875,7 @@ public void testImprovedMatching() throws IOException { /** Names that fuzzy match to higher species "Iberus gualtieranus" */ @Test public void testIberusGualtieranus() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("Iberus gualterianus minor Serradell", cl, 4564258, new IntRange(90, 99)); cl.setFamily("Helicidae"); @@ -885,7 +885,7 @@ public void testIberusGualtieranus() throws IOException { /** https://github.com/gbif/portal-feedback/issues/2930 */ @Test public void higherOverFuzzy() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); // Stolas NameUsageMatch m = null; m = assertMatch("Stolas costaricensis", cl, 4734997, new IntRange(90, 99)); @@ -915,7 +915,7 @@ public void higherOverFuzzy() throws IOException { /** https://github.com/gbif/checklistbank/issues/192 */ @Test public void subgenusJacea() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Plantae"); // THIS SETS the genus too NameUsageMatch m = assertMatch("Centaurea subg. Jacea", cl, 7652419, MatchType.EXACT); @@ -925,7 +925,7 @@ public void subgenusJacea() throws IOException { /** https://github.com/gbif/checklistbank/issues/196 */ @Test public void mycenaFllavoalba() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Fungi"); cl.setPhylum("Basidiomycota"); cl.setClazz("Agaricomycetes"); @@ -943,7 +943,7 @@ public void mycenaFllavoalba() throws IOException { /** https://github.com/gbif/checklistbank/issues/200 */ @Test public void merganser() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); NameUsageMatch m = assertMatch("Mergus merganser Linnaeus, 1758", cl, 2498370, MatchType.EXACT); assertEquals("Mergus merganser", m.getUsage().getCanonicalName()); @@ -956,7 +956,7 @@ public void merganser() throws IOException { /** https://github.com/gbif/checklistbank/issues/175 */ @Test public void otuIncaseInsensitive() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertMatch("AAA536-G10 sp003284565", cl, 10701019, MatchType.EXACT); assertMatch("aaa536-g10 sp003284565", cl, 10701019, MatchType.EXACT); @@ -965,7 +965,7 @@ public void otuIncaseInsensitive() throws IOException { @Test public void npe() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); assertNoMatch(null, cl); assertNoMatch("", cl); @@ -974,7 +974,7 @@ public void npe() throws IOException { @Test public void dinosaura() throws IOException { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setPhylum("Chordata"); cl.setClazz("Reptilia"); @@ -998,7 +998,7 @@ public void testCommonHigherDenomiator() throws Exception { /** https://github.com/gbif/portal-feedback/issues/4532 */ @Test public void testAuthorBrackets() throws Exception { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); assertMatch("Eristalis lineata Harris, 1776", Rank.SPECIES, cl, 7834133, MatchType.EXACT); @@ -1008,7 +1008,7 @@ public void testAuthorBrackets() throws Exception { /** https://github.com/gbif/portal-feedback/issues/2935 */ @Test public void aggregates() throws Exception { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setOrder("Diptera"); cl.setFamily("Clusiidae"); @@ -1021,7 +1021,7 @@ public void aggregates() throws Exception { /** https://github.com/gbif/checklistbank/issues/280 */ @Test public void iris() throws Exception { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setPhylum("Chordata"); cl.setClazz("Aves"); @@ -1036,7 +1036,7 @@ public void iris() throws Exception { /** https://github.com/gbif/checklistbank/issues/289 */ @Test public void shortCircuitUsageKey() { - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setGenus("Ablabera"); cl.setSpecies("rufipes"); diff --git a/matching-ws/src/test/java/life/catalogue/matching/NameNRankTest.java b/matching-ws/src/test/java/life/catalogue/matching/NameNRankTest.java index ea6218d86..3e339c758 100644 --- a/matching-ws/src/test/java/life/catalogue/matching/NameNRankTest.java +++ b/matching-ws/src/test/java/life/catalogue/matching/NameNRankTest.java @@ -12,7 +12,7 @@ void build() { assertName("Asteraceae Mill.", "Asteraceae", "Mill."); assertName("Lepidothrix iris L.", "Lepidothrix iris", "L.", "iris", "", null); - LinneanClassification cl = new LinneanClassificationImpl(); + LinneanClassification cl = new Classification(); cl.setKingdom("Animalia"); cl.setPhylum("Chordata"); cl.setClazz("Aves"); diff --git a/matching-ws/src/test/java/life/catalogue/matching/NameUsageBuilder.java b/matching-ws/src/test/java/life/catalogue/matching/NameUsageBuilder.java index f48bd5d33..abdeaa798 100644 --- a/matching-ws/src/test/java/life/catalogue/matching/NameUsageBuilder.java +++ b/matching-ws/src/test/java/life/catalogue/matching/NameUsageBuilder.java @@ -44,7 +44,7 @@ public static NameUsageMatch newNameUsageMatch( m.getUsage().setCanonicalName(canonicalName); m.getUsage().setRank(rank); m.setAcceptedUsage(new RankedName(acceptedUsageKey, null, null)); - m.setStatus(status); + m.getDiagnostics().setStatus(status); m.getDiagnostics().setConfidence(confidence); m.getDiagnostics().setNote(note); m.getDiagnostics().setMatchType(matchType);