001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.api.model.occurrence; 015 016import org.gbif.api.vocabulary.Country; 017import org.gbif.api.vocabulary.EndpointType; 018import org.gbif.api.vocabulary.GbifRegion; 019import org.gbif.dwc.terms.Term; 020import org.gbif.dwc.terms.TermFactory; 021 022import java.util.Date; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.StringJoiner; 029import java.util.UUID; 030 031import com.fasterxml.jackson.annotation.JsonProperty; 032 033import javax.annotation.Nullable; 034import javax.validation.constraints.NotNull; 035 036import org.apache.commons.lang3.StringUtils; 037 038import com.fasterxml.jackson.annotation.JsonAnyGetter; 039import com.fasterxml.jackson.annotation.JsonAnySetter; 040import com.fasterxml.jackson.annotation.JsonIgnore; 041 042import io.swagger.v3.oas.annotations.media.Schema; 043 044/** 045 * An extended map holding all core terms of an occurrence record. 046 * Major extensions that we index are also supported, i.e. media, identifiers and measurements or facts. 047 */ 048@SuppressWarnings("unused") 049public class VerbatimOccurrence { 050 051 @Schema( 052 description = "Unique GBIF key for the occurrence.\n\n" + 053 "We aim to keep these keys stable, but this is not possible in every case." 054 ) 055 private Long key; 056 057 @Schema( 058 description = "The UUID of the GBIF dataset containing this occurrence." 059 ) 060 private UUID datasetKey; 061 062 @Schema( 063 description = "The UUID of the organization which publishes the dataset containing this occurrence." 064 ) 065 private UUID publishingOrgKey; 066 067 @Schema( 068 description = "Any networks to which the dataset containing this occurrence is registered." 069 ) 070 private List<UUID> networkKeys; 071 072 @Schema( 073 description = "The UUID of the technical installation hosted the dataset containing this occurrence." 074 ) 075 private UUID installationKey; 076 077 @Schema( 078 description = "The UUID of the publishing organization which operates the technical installation hosting the " + 079 "dataset containing this occurrence." 080 ) 081 private UUID hostingOrganizationKey; 082 083 @Schema( 084 description = "The country, territory or island based on ISO-3166 of the organization publishing the dataset " + 085 "containing this occurrence." 086 ) 087 private Country publishingCountry; 088 089 @Schema( 090 description = "The technical protocol by which this occurrence was retrieved from the publisher's systems." 091 ) 092 private EndpointType protocol; 093 094 @Schema( 095 description = "The time this occurrence was last retrieved from the publisher's systems." 096 ) 097 private Date lastCrawled; 098 099 @Schema( 100 description = "The time this occurrence was last processed by GBIF's interpretation system “Pipelines”.\n\n" + 101 "This is the time the record was last changed in GBIF, **not** the time the record was last changed by the " + 102 "publisher. Data is also reprocessed when we changed the taxonomic backbone, geographic data sources or " + 103 "other interpretation procedures.\n\n" + 104 "An earlier interpretation system distinguished between “parsing” and “interpretation”, but in the current " + 105 "system there is only one process — the two dates will always be the same." 106 ) 107 private Date lastParsed; 108 109 @Schema( 110 description = "The sequence number of the attempt by GBIF to download (”crawl”), interpret and index the dataset " + 111 "to which this occurrence belongs." 112 ) 113 private Integer crawlId; 114 115 /** GBIF Participation: Programme and Project */ 116 @Schema( 117 description = "The identifier for a project, often assigned by a funded programme." 118 ) 119 private String projectId; 120 121 @Schema( 122 description = "The identifier for a programme which funded the digitization of this occurrence." 123 ) 124 private String programmeAcronym; 125 126 // the verbatim fields for the occurrence 127 @Schema( 128 description = "The verbatim fields for the occurrence, with Darwin Core terms as keys." 129 ) 130 private Map<Term, String> verbatimFields = new HashMap<>(); 131 132 // verbatim extension data 133 @Schema( 134 description = "The verbatim Darwin Core Archive extension fields for this occurrence.\n\n" + 135 "The main key is the record class term (the row type in Darwin Core Archive), within that are " + 136 " values with extension terms as keys." 137 ) 138 private Map<String, List<Map<Term, String>>> extensions = new HashMap<>(); 139 140 /** 141 * Get the value of a specific field (Term). 142 */ 143 @Nullable 144 public String getVerbatimField(Term term) { 145 Objects.requireNonNull(term, "term can't be null"); 146 return verbatimFields.get(term); 147 } 148 149 /** 150 * @return true if a verbatim field exists and is not null or an empty string 151 */ 152 public boolean hasVerbatimField(Term term) { 153 Objects.requireNonNull(term, "term can't be null"); 154 return StringUtils.isNotEmpty(verbatimFields.get(term)); 155 } 156 157 /** 158 * For setting a specific field without having to replace the entire verbatimFields Map. 159 * 160 * @param term the field to set 161 * @param fieldValue the field's value 162 */ 163 public void setVerbatimField(Term term, @Nullable String fieldValue) { 164 Objects.requireNonNull(term, "term can't be null"); 165 verbatimFields.put(term, fieldValue); 166 } 167 168 /** 169 * The GBIF assigned, persistent key to the occurrence record. 170 * OccurrenceID itself is kept in the verbatim verbatimFields map. 171 */ 172 @NotNull 173 public Long getKey() { 174 return key; 175 } 176 177 public void setKey(Long key) { 178 this.key = key; 179 } 180 181 @NotNull 182 public UUID getDatasetKey() { 183 return datasetKey; 184 } 185 186 public void setDatasetKey(UUID datasetKey) { 187 this.datasetKey = datasetKey; 188 } 189 190 @NotNull 191 public UUID getPublishingOrgKey() { 192 return publishingOrgKey; 193 } 194 195 public void setPublishingOrgKey(UUID publishingOrgKey) { 196 this.publishingOrgKey = publishingOrgKey; 197 } 198 199 /** 200 * The GBIF Network associated to the publishing dataset. 201 */ 202 @Nullable 203 public List<UUID> getNetworkKeys() { 204 return networkKeys; 205 } 206 207 public void setNetworkKeys(List<UUID> networkKeys) { 208 this.networkKeys = networkKeys; 209 } 210 211 /** 212 * Technical installation that publishes this occurrence record. 213 */ 214 @Nullable 215 public UUID getInstallationKey() { 216 return installationKey; 217 } 218 219 public void setInstallationKey(UUID installationKey) { 220 this.installationKey = installationKey; 221 } 222 223 /** 224 * The country of the organization that publishes the dataset to which the occurrence belongs. 225 */ 226 @Nullable 227 public Country getPublishingCountry() { 228 return publishingCountry; 229 } 230 231 public void setPublishingCountry(Country publishingCountry) { 232 this.publishingCountry = publishingCountry; 233 } 234 235 @Nullable 236 @JsonProperty("publishedByGbifRegion") 237 public GbifRegion getPublishedByGbifRegion() { 238 return Optional.ofNullable(publishingCountry).map(Country::getGbifRegion).orElse(null); 239 } 240 241 public void setPublishedByGbifRegion(String gbifRegion) { 242 // ignore, setter only to avoid JSON being written into the fields map 243 } 244 245 @NotNull 246 public EndpointType getProtocol() { 247 return protocol; 248 } 249 250 public void setProtocol(EndpointType protocol) { 251 this.protocol = protocol; 252 } 253 254 /** 255 * The date this record was last crawled/harvested from the endpoint. 256 */ 257 @Nullable 258 public Date getLastCrawled() { 259 return lastCrawled == null ? null : new Date(lastCrawled.getTime()); 260 } 261 262 public void setLastCrawled(@Nullable Date lastCrawled) { 263 this.lastCrawled = lastCrawled == null ? null : new Date(lastCrawled.getTime()); 264 } 265 266 /** 267 * The date this record was last parsed from raw xml/json into verbatim verbatimFields. 268 */ 269 @Nullable 270 public Date getLastParsed() { 271 return lastParsed; 272 } 273 274 public void setLastParsed(@Nullable Date lastParsed) { 275 this.lastParsed = lastParsed == null ? null : new Date(lastParsed.getTime()); 276 } 277 278 /** 279 * Crawling attempt id. 280 */ 281 @Nullable 282 public Integer getCrawlId() { 283 return crawlId; 284 } 285 286 public void setCrawlId(Integer crawlId) { 287 this.crawlId = crawlId; 288 } 289 290 /** 291 * GBIF project identifier. 292 */ 293 @Nullable 294 public String getProjectId() { 295 return projectId; 296 } 297 298 public void setProjectId(String projectId) { 299 this.projectId = projectId; 300 } 301 302 /** 303 * GBIF programme acronym/identifier. 304 */ 305 @Nullable 306 public String getProgrammeAcronym() { 307 return programmeAcronym; 308 } 309 310 public void setProgrammeAcronym(String programmeAcronym) { 311 this.programmeAcronym = programmeAcronym; 312 } 313 314 /** 315 * Organization key of the installation that hosts the occurrence record. 316 */ 317 @Nullable 318 public UUID getHostingOrganizationKey() { 319 return hostingOrganizationKey; 320 } 321 322 public void setHostingOrganizationKey(UUID hostingOrganizationKey) { 323 this.hostingOrganizationKey = hostingOrganizationKey; 324 } 325 326 /** 327 * A map holding all verbatim core terms. 328 */ 329 @NotNull 330 @JsonIgnore 331 public Map<Term, String> getVerbatimFields() { 332 return verbatimFields; 333 } 334 335 public void setVerbatimFields(Map<Term, String> verbatimFields) { 336 this.verbatimFields = verbatimFields; 337 } 338 339 /** 340 * A map holding all verbatim extension terms. 341 */ 342 @NotNull 343 public Map<String, List<Map<Term, String>>> getExtensions() { 344 return extensions; 345 } 346 347 public void setExtensions(Map<String, List<Map<Term, String>>> extensions) { 348 this.extensions = extensions; 349 } 350 351 @Override 352 public boolean equals(Object o) { 353 if (this == o) { 354 return true; 355 } 356 if (o == null || getClass() != o.getClass()) { 357 return false; 358 } 359 VerbatimOccurrence that = (VerbatimOccurrence) o; 360 return Objects.equals(key, that.key) && 361 Objects.equals(datasetKey, that.datasetKey) && 362 Objects.equals(publishingOrgKey, that.publishingOrgKey) && 363 Objects.equals(networkKeys, that.networkKeys) && 364 Objects.equals(installationKey, that.installationKey) && 365 publishingCountry == that.publishingCountry && 366 protocol == that.protocol && 367 Objects.equals(lastCrawled, that.lastCrawled) && 368 Objects.equals(lastParsed, that.lastParsed) && 369 Objects.equals(crawlId, that.crawlId) && 370 Objects.equals(projectId, that.projectId) && 371 Objects.equals(programmeAcronym, that.programmeAcronym) && 372 Objects.equals(verbatimFields, that.verbatimFields) && 373 Objects.equals(extensions, that.extensions); 374 } 375 376 @Override 377 public int hashCode() { 378 return Objects 379 .hash(key, datasetKey, publishingOrgKey, networkKeys, installationKey, publishingCountry, 380 protocol, lastCrawled, lastParsed, crawlId, projectId, programmeAcronym, verbatimFields, 381 extensions); 382 } 383 384 @Override 385 public String toString() { 386 return new StringJoiner(", ", VerbatimOccurrence.class.getSimpleName() + "[", "]") 387 .add("key=" + key) 388 .add("datasetKey=" + datasetKey) 389 .add("publishingOrgKey=" + publishingOrgKey) 390 .add("networkKeys=" + networkKeys) 391 .add("installationKey=" + installationKey) 392 .add("publishingCountry=" + publishingCountry) 393 .add("protocol=" + protocol) 394 .add("lastCrawled=" + lastCrawled) 395 .add("lastParsed=" + lastParsed) 396 .add("crawlId=" + crawlId) 397 .add("projectId='" + projectId + "'") 398 .add("programmeAcronym='" + programmeAcronym + "'") 399 .add("extensions=" + extensions) 400 .toString(); 401 } 402 403 /** 404 * This private method is only for deserialization via jackson and not exposed anywhere else! 405 */ 406 @JsonAnySetter 407 private void addJsonVerbatimField(String key, String value) { 408 if(StringUtils.isNotEmpty(value)) { 409 Term t = TermFactory.instance().findTerm(key); 410 verbatimFields.put(t, value); 411 } 412 } 413 414 /** 415 * This private method is only for serialization via jackson and not exposed anywhere else! 416 * It maps the verbatimField terms into properties with their full qualified name. 417 */ 418 @JsonAnyGetter 419 private Map<String, String> jsonVerbatimFields() { // note: for 1.6.0 MUST use non-getter name; otherwise doesn't matter 420 Map<String, String> extendedProps = new HashMap<>(); 421 for (Map.Entry<Term, String> prop : verbatimFields.entrySet()) { 422 extendedProps.put(prop.getKey().qualifiedName(), prop.getValue()); 423 } 424 return extendedProps; 425 } 426}