001/* 002 * Copyright 2021 Global Biodiversity Information Facility (GBIF) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.gbif.dwc.terms; 017 018import com.google.common.collect.Sets; 019import com.google.common.reflect.ClassPath; 020import org.junit.jupiter.api.Test; 021 022import java.util.Arrays; 023import java.util.HashSet; 024import java.util.Set; 025import java.util.concurrent.ExecutorService; 026import java.util.concurrent.Executors; 027import java.util.concurrent.TimeUnit; 028import java.util.stream.Collectors; 029 030import static org.junit.jupiter.api.Assertions.assertEquals; 031import static org.junit.jupiter.api.Assertions.assertFalse; 032import static org.junit.jupiter.api.Assertions.assertNotEquals; 033import static org.junit.jupiter.api.Assertions.assertThrows; 034 035public class TermFactoryTest { 036 037 final TermFactory TF = TermFactory.instance(); 038 039 /** 040 * GBIF code assumes a term coming from any of the Term enumerations mostly have unique simple names. 041 * This tests verifies that! 042 * 043 * AcefTerm is known to overlap, so its excluded, see skipSimple in AcefTermTest. 044 */ 045 @Test 046 public void testKnownTermUniqueness() { 047 Set<String> names = new HashSet<>(); 048 049 addTerms(names, DwcTerm.values()); 050 addTerms(names, DcTerm.values()); 051 addTerms(names, GbifTerm.values()); 052 addTerms(names, GbifInternalTerm.values()); 053 addTerms(names, IucnTerm.values()); 054 //addTerms(names, DcElement.values()); 055 //addTerms(names, AcefTerm.values()); 056 //addTerms(names, PlaziTerm.values()); 057 addTerms(names, GadmTerm.values()); 058 //addTerms(names, DwcaTerm.values()); 059 060 // Audubon Core 061 addTerms(names, termsBut(AcTerm.values(), AcTerm.Multimedia, AcTerm.relatedResourceID)); 062 addTerms(names, ExifTerm.values()); 063 addTerms(names, IptcTerm.values()); 064 addTerms(names, PhotoshopTerm.values()); 065 addTerms(names, XmpTerm.values()); 066 addTerms(names, XmpRightsTerm.values()); 067 068 // Terms for extensions supported in GBIF downloads. 069 addTerms(names, ChronoTerm.values()); 070 addTerms(names, GbifDnaTerm.values()); 071 addTerms(names, GbifMiqeTerm.values()); 072 addTerms(names, GermplasmTerm.values()); 073 addTerms(names, termsBut(GgbnTerm.values(), GgbnTerm.MaterialSample)); 074 addTerms(names, MixsTerm.values()); 075 addTerms(names, ObisTerm.values()); 076 addTerms(names, Wgs84GeoPositioningTerm.values()); 077 } 078 079 private Term[] termsBut(Term[] terms, Term... exclude) { 080 Set<Term> excl = Sets.newHashSet(exclude); 081 return Arrays.stream(terms) 082 .filter(t -> !excl.contains(t)) 083 .collect(Collectors.toList()).toArray(new Term[]{}); 084 } 085 086 private void addTerms(Set<String> names, Term[] terms) { 087 for (Term t : terms) { 088 assertFalse(names.contains(t.simpleName()), "Duplicate simple name " + t.simpleName() + " for " + t); 089 if (t instanceof AlternativeNames) { 090 for (String a : ((AlternativeNames) t).alternativeNames()) { 091 assertFalse(names.contains(a), "Duplicate alternative name " + a + " for " + t); 092 names.add(a); 093 } 094 } 095 names.add(t.simpleName()); 096 } 097 } 098 099 @Test 100 public void testCompleteness() throws Exception { 101 for (ClassPath.ClassInfo info : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(DwcTerm.class.getPackage().getName())) { 102 Class<?> cl = info.load(); 103 if (cl.isEnum() && Term.class.isAssignableFrom(cl)) { 104 Class<Term> tcl = (Class<Term>) cl; 105 for (Term t : tcl.getEnumConstants()) { 106 assertEquals(t, TF.findTerm(t.qualifiedName()), "Unknown term " + t.qualifiedName()); 107 assertEquals(t, TF.findTerm(t.prefixedName()), "Unknown term " + t.prefixedName()); 108 } 109 } 110 } 111 } 112 113 @Test 114 public void testFindTerm() { 115 assertEquals(DwcTerm.scientificName, TF.findTerm("ScientificName")); 116 assertEquals(DwcTerm.scientificName, TF.findTerm("dwc:scientificName")); 117 assertEquals(DwcTerm.scientificName, TF.findTerm("http://rs.tdwg.org/dwc/terms/scientificName")); 118 assertEquals(DcElement.identifier, TF.findTerm("dc:identifier")); 119 assertEquals(GbifTerm.Identifier, TF.findTerm("Identifier")); 120 assertEquals(GbifTerm.Identifier, TF.findClassTerm("identifier")); 121 assertEquals(DcTerm.identifier, TF.findTerm("identifier")); 122 assertEquals(DcTerm.identifier, TF.findPropertyTerm("identifier")); 123 assertEquals(DcTerm.identifier, TF.findTerm("id")); 124 assertEquals(DwcTerm.parentNameUsageID, TF.findTerm("dwc:higherNameUsageID")); 125 assertEquals(DwcTerm.acceptedNameUsageID, TF.findTerm("dwc:acceptedTaxonId")); 126 assertEquals(DwcTerm.acceptedNameUsageID, TF.findTerm("dwc:acceptedTaxonID")); 127 assertEquals(DwcTerm.acceptedNameUsageID, TF.findTerm("acceptedTaxonID")); 128 assertEquals(DwcTerm.acceptedNameUsageID, TF.findTerm("http://rs.tdwg.org/dwc/terms/acceptedTaxonId")); 129 assertEquals(DwcTerm.acceptedNameUsageID, TF.findPropertyTerm("dwc:acceptedTaxonId")); 130 assertEquals(DwcTerm.acceptedNameUsageID, TF.findPropertyTerm("dwc:acceptedTaxonID")); 131 assertEquals(DwcTerm.acceptedNameUsageID, TF.findPropertyTerm("acceptedTaxonID")); 132 assertEquals(DwcTerm.acceptedNameUsageID, TF.findPropertyTerm("http://rs.tdwg.org/dwc/terms/acceptedTaxonId")); 133 assertEquals(AcefTerm.AcceptedTaxonID, TF.findPropertyTerm("AcceptedTaxonID")); 134 assertEquals(AcefTerm.AcceptedTaxonID, TF.findTerm("acef:AcceptedTaxonID")); 135 assertEquals(AcefTerm.AcceptedTaxonID, TF.findPropertyTerm("acef:AcceptedTaxonID")); 136 137 assertEquals(DwcTerm.vernacularName, TF.findTerm("dwc:vernacularName")); 138 assertEquals(DwcTerm.vernacularName, TF.findTerm("vernacularName")); 139 assertEquals(GbifTerm.VernacularName, TF.findTerm("VernacularName")); 140 assertEquals(GbifTerm.VernacularName, TF.findTerm("gbif:VernacularName")); 141 142 assertEquals(GbifInternalTerm.unitQualifier, TF.findTerm("UNIT_QUALIFIER")); 143 144 assertEquals("threatStatus", TF.findTerm("http://rs.gbif.org/terms/1.0/threatStatus").simpleName()); 145 assertEquals("threatStatus", TF.findTerm("http://rs.gbif.org/terms/1321.43/threatStatus").simpleName()); 146 147 assertEquals(DwcTerm.catalogNumber, TF.findTerm("\"catalogNumber\"")); 148 assertEquals(AcefTerm.Details, TF.findTerm("acef:source")); 149 assertEquals(DwcTerm.family, TF.findTerm("dwc:family")); 150 assertEquals(DwcTerm.family, TF.findTerm("family")); 151 assertEquals(AcefTerm.Family, TF.findTerm("acef:family")); 152 153 assertEquals(DwcaTerm.ID, TF.findTerm("dwca:ID")); 154 155 assertEquals(BibTexTerm.CLASS_TERM, TF.findTerm("bib:BibTeX")); 156 assertEquals(BibTexTerm.CLASS_TERM, TF.findTerm("http://bibtex.org/BibTeX")); 157 158 Term t = BibTexTerm.buildFromURI("http://bibtex.org/creator"); 159 assertEquals(t, TF.findTerm("http://bibtex.org/creator")); 160 assertEquals(t, TF.findTerm("bib:creator")); 161 162 // ACEF namespace has changed: https://github.com/gbif/portal-feedback/issues/4890 163 assertEquals(AcefTerm.Country, TF.findTerm("acef:Country")); 164 assertEquals(AcefTerm.Country, TF.findTerm("http://rs.col.plus/terms/acef/Country")); 165 assertEquals(AcefTerm.Country, TF.findTerm("https://rs.col.plus/terms/acef/Country")); 166 assertEquals(DwcTerm.country, TF.findTerm("country")); 167 168 // MIxS uses unreadable identifiers in qualified names. 169 assertEquals(MixsTerm.samp_size, TF.findTerm("samp_size")); 170 assertEquals(MixsTerm.samp_size, TF.findTerm("mixs:samp_size")); 171 assertEquals(MixsTerm.samp_size, TF.findTerm("https://w3id.org/gensc/terms/MIXS:0000001")); 172 assertEquals(MixsTerm.samp_size, TF.findTerm("https://w3id.org/mixs/0000001")); 173 assertEquals(MixsTerm.lib_reads_seqd, TF.findTerm("http://gensc.org/ns/mixs/lib_reads_seqd")); 174 assertEquals(MixsTerm.assembly_name, TF.findTerm("http://gensc.org/ns/mixs/assembly")); 175 assertEquals(MixsTerm.assembly_qual, TF.findTerm("http://gensc.org/ns/mixs/finishing_strategy")); 176 assertEquals(MixsTerm.annot, TF.findTerm("http://gensc.org/ns/mixs/annot_source")); 177 } 178 179 @Test 180 public void addUnknownTerm() { 181 TermFactory factory = TermFactory.instance(); 182 183 Term me1 = factory.findTerm("http://me.com/#me"); 184 Term me2 = factory.findTerm("http://me.com/me"); 185 Term me3 = factory.findTerm("http://me.org/me"); 186 187 assertNotEquals(me1, me2); 188 assertNotEquals(me1, me3); 189 assertNotEquals(me2, me3); 190 } 191 192 @Test 193 public void badTerm() { 194 TermFactory factory = TermFactory.instance(); 195 assertThrows(IllegalArgumentException.class, () -> factory.findTerm("Hallo Tim")); 196 } 197 198 @Test 199 public void removedGbifTerms() { 200 assertEquals(DwcTerm.genericName, TF.findTerm("genericName")); 201 assertEquals(DwcTerm.recordedByID, TF.findTerm("recordedByID")); 202 assertEquals(DwcTerm.identifiedByID, TF.findTerm("identifiedByID")); 203 204 assertEquals(DwcTerm.genericName, TF.findTerm("http://rs.gbif.org/terms/1.0/genericName")); 205 assertEquals(DwcTerm.recordedByID, TF.findTerm("http://rs.gbif.org/terms/1.0/recordedByID")); 206 assertEquals(DwcTerm.identifiedByID, TF.findTerm("http://rs.gbif.org/terms/1.0/identifiedByID")); 207 208 assertEquals(DwcTerm.genericName, TF.findTerm("gbif:genericName")); 209 assertEquals(DwcTerm.recordedByID, TF.findTerm("gbif:recordedByID")); 210 assertEquals(DwcTerm.identifiedByID, TF.findTerm("gbif:identifiedByID")); 211 } 212 213 @Test 214 public void addSimpleTerm() { 215 TermFactory factory = TermFactory.instance(); 216 217 Term hallo = factory.findTerm("hallo"); 218 assertEquals(UnknownTerm.class, hallo.getClass()); 219 assertEquals("http://unknown.org/hallo", hallo.qualifiedName()); 220 assertEquals("hallo", hallo.simpleName()); 221 222 223 Term tim = factory.findTerm("Tim"); 224 assertEquals(UnknownTerm.class, tim.getClass()); 225 assertEquals("http://unknown.org/Tim", tim.qualifiedName()); 226 assertEquals("http://unknown.org", tim.namespace().toString()); 227 assertEquals("Tim", tim.simpleName()); 228 229 Term eva = factory.findTerm("tim:Eva"); 230 assertEquals(UnknownTerm.class, eva.getClass()); 231 assertEquals("http://unknown.org/tim/Eva", eva.qualifiedName()); 232 assertEquals("http://unknown.org", tim.namespace().toString()); 233 assertEquals("http://unknown.org/tim/Eva", eva.qualifiedName()); 234 assertEquals("tim:Eva", eva.prefixedName()); 235 assertEquals("Eva", eva.simpleName()); 236 237 assertNotEquals(hallo, tim); 238 } 239 240 @Test 241 public void addUnknownSimpleTerm() { 242 TermFactory factory = TermFactory.instance(); 243 244 Term t1 = factory.findTerm("me"); 245 Term t2 = factory.findTerm("me"); 246 Term t3 = factory.findTerm("Ne"); 247 248 assertEquals(t1, t2); 249 assertNotEquals(t1, t3); 250 assertNotEquals(t2, t3); 251 } 252 253 /** 254 * Not a real test, just a way of running many concurrent TermFactory.instance() calls to verify thread safety. 255 */ 256 @Test 257 public void testMultithreadStart() throws InterruptedException { 258 int threadCount = 100; 259 ExecutorService tp = Executors.newFixedThreadPool(threadCount); 260 for (int i = 0; i < threadCount; i++) { 261 tp.submit(new TermFactoryLoader()); 262 } 263 tp.shutdown(); 264 tp.awaitTermination(30, TimeUnit.SECONDS); 265 } 266 267 private static class TermFactoryLoader implements Runnable { 268 @Override 269 public void run() { 270 TermFactory.instance(); 271 } 272 } 273}