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.collections; 015 016import com.fasterxml.jackson.annotation.JsonIgnore; 017import io.swagger.v3.oas.annotations.Hidden; 018import io.swagger.v3.oas.annotations.media.Schema; 019import java.net.URI; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.List; 023import java.util.Objects; 024import java.util.UUID; 025import javax.annotation.Nullable; 026import javax.validation.Valid; 027import javax.validation.constraints.NotNull; 028import javax.validation.constraints.Size; 029import lombok.EqualsAndHashCode; 030import lombok.Getter; 031import lombok.Setter; 032import lombok.ToString; 033import org.gbif.api.model.common.DOI; 034import org.gbif.api.model.registry.Comment; 035import org.gbif.api.model.registry.Identifier; 036import org.gbif.api.model.registry.LenientEquals; 037import org.gbif.api.model.registry.MachineTag; 038import org.gbif.api.model.registry.PrePersist; 039import org.gbif.api.model.registry.Tag; 040import org.gbif.api.util.HttpURI; 041import org.gbif.api.util.LenientEqualsUtils; 042import org.gbif.api.util.validators.email.ValidEmail; 043import org.gbif.api.vocabulary.License; 044import org.gbif.api.vocabulary.collections.MasterSourceType; 045 046/** 047 * A group of specimens or other natural history objects. Types of collections can be: specimens, 048 * original artwork, archives, observations, library materials, datasets, photographs or mixed 049 * collections such as those that result from expeditions and voyages of discovery. 050 */ 051@SuppressWarnings("unused") 052@ToString 053@EqualsAndHashCode 054public class Collection implements CollectionEntity, LenientEquals<Collection> { 055 056 @Schema( 057 description = "Unique GBIF key for the collection.", 058 accessMode = Schema.AccessMode.READ_ONLY) 059 private UUID key; 060 061 @Schema( 062 description = 063 "Code of the collection — identifies a collection at the " 064 + "owner's location.\n\n" 065 + "*(NB Not required for updates.)*") 066 @Sourceable(masterSources = MasterSourceType.IH) 067 private String code; 068 069 @Schema( 070 description = "Descriptive name of the collection.\n\n" + "*(NB Not required for updates.)*") 071 @Sourceable(masterSources = MasterSourceType.GBIF_REGISTRY) 072 @Sourceable(masterSources = MasterSourceType.IH, overridable = true) 073 private String name; 074 075 @Schema(description = "Description or summary of the contents of the collection.") 076 @Sourceable(masterSources = MasterSourceType.GBIF_REGISTRY) 077 private String description; 078 079 /** 080 * Content type of the elements found in a collection. 081 */ 082 @Getter 083 @Setter 084 @Schema(description = "Content type of the elements found in the collection.") 085 private List<String> contentTypes = new ArrayList<>(); 086 087 @Schema(description = "Whether the collection is active/maintained.") 088 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 089 private boolean active; 090 091 /** 092 * Does this collections belong to an individual?. 093 */ 094 @Setter 095 @Getter 096 @Schema(description = "Whether this collection belongs to an individual.") 097 private boolean personalCollection; 098 099 /** 100 * Digital Object Identifier assigned to this collection. 101 */ 102 @Setter 103 @Getter 104 @Schema( 105 description = "A Digital Object Identifier for the collection.", 106 implementation = String.class, 107 pattern = "(10(?:\\.[0-9]+)+)" + "/(.+)") 108 private DOI doi; 109 110 @Schema(description = "Email addresses associated with the collection.") 111 @Sourceable(masterSources = MasterSourceType.IH) 112 private List<@ValidEmail String> email = new ArrayList<>(); 113 114 @Schema(description = "Telephone numbers associated with the collection.") 115 @Sourceable(masterSources = MasterSourceType.IH) 116 private List<String> phone = new ArrayList<>(); 117 118 @Setter 119 @Schema(description = "The collection's WWW homepage.") 120 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 121 private URI homepage; 122 123 @Setter 124 @Schema(description = "URLs for interactive catalogues of the collection.") 125 private List<@HttpURI URI> catalogUrls = new ArrayList<>(); 126 127 @Setter 128 @Schema(description = "URLs for machine-readable APIs for the collection catalogue.") 129 private List<@HttpURI URI> apiUrls = new ArrayList<>(); 130 131 /** 132 * Types of preservation mechanisms used for these collections. 133 */ 134 @Setter 135 @Getter 136 @Schema(description = "The preservation mechanisms used for this collection.") 137 @Sourceable(masterSources = MasterSourceType.GBIF_REGISTRY) 138 private List<String> preservationTypes = new ArrayList<>(); 139 140 /** 141 * Defines how a collection as been added or joined. 142 */ 143 @Setter 144 @Getter 145 @Schema(description = "How the collection was added or joined.") 146 private String accessionStatus; 147 148 /** 149 * Institution that owns or hosts this collection. 150 */ 151 @Setter 152 @Getter 153 @Schema(description = "The key of the institution owning or hosting the collection.") 154 private UUID institutionKey; 155 156 @Schema(description = "The postal address of the collection.") 157 @Sourceable(masterSources = MasterSourceType.IH) 158 private Address mailingAddress; 159 160 @Schema(description = "The address of the location of the collection.") 161 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 162 private Address address; 163 164 @Schema( 165 description = 166 "The GBIF username of the creator of the collection entity in " + "the GBIF registry.", 167 accessMode = Schema.AccessMode.READ_ONLY) 168 private String createdBy; 169 170 @Schema( 171 description = 172 "The GBIF username of the last user to modify the collection " 173 + "entity in the GBIF registry.", 174 accessMode = Schema.AccessMode.READ_ONLY) 175 private String modifiedBy; 176 177 @Schema( 178 description = 179 "Timestamp of when the collection entity was created in the GBIF " + "registry.", 180 accessMode = Schema.AccessMode.READ_ONLY) 181 private Date created; 182 183 @Schema( 184 description = 185 "Timestamp of when the collection entity was modified in the GBIF " + "registry.", 186 accessMode = Schema.AccessMode.READ_ONLY) 187 private Date modified; 188 189 @Schema( 190 description = 191 "If present, the collection was deleted at this time. " 192 + "It is possible for it to be restored in the future.", 193 accessMode = Schema.AccessMode.READ_ONLY) 194 private Date deleted; 195 196 @Schema( 197 description = "A list of tags associated with this collection.", 198 accessMode = Schema.AccessMode.READ_ONLY) 199 private List<Tag> tags = new ArrayList<>(); 200 201 @Schema( 202 description = "A list of identifiers associated with this collection.", 203 accessMode = Schema.AccessMode.READ_ONLY) 204 @Sourceable(masterSources = MasterSourceType.IH, sourceableParts = "IH_IRN") 205 @Sourceable(masterSources = MasterSourceType.GBIF_REGISTRY, sourceableParts = "DOI") 206 private List<Identifier> identifiers = new ArrayList<>(); 207 208 @Schema( 209 description = "A list of contact people for this collection.", 210 accessMode = Schema.AccessMode.READ_ONLY) 211 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 212 private List<Contact> contactPersons = new ArrayList<>(); 213 214 @Setter 215 @Getter 216 @Schema(description = "The number of specimens contained in this collection.") 217 @Sourceable(masterSources = MasterSourceType.IH) 218 private Integer numberSpecimens; 219 220 @Schema( 221 description = "A list of machine tags associated with this collection.", 222 accessMode = Schema.AccessMode.READ_ONLY) 223 private List<MachineTag> machineTags = new ArrayList<>(); 224 225 @Setter 226 @Getter 227 @Schema(description = "The taxonomic coverage of this collection.") 228 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 229 private String taxonomicCoverage; 230 231 @Setter 232 @Getter 233 @Schema(description = "The geographic coverage of this collection.") 234 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 235 private String geographicCoverage; 236 237 @Setter 238 @Getter 239 @Schema(description = "Notes on the collection.") 240 @Sourceable(masterSources = MasterSourceType.IH) 241 private String notes; 242 243 @Setter 244 @Getter 245 @Schema(description = "Other collections incorporated into this collection.") 246 @Sourceable(masterSources = {MasterSourceType.GBIF_REGISTRY, MasterSourceType.IH}) 247 private List<String> incorporatedCollections = new ArrayList<>(); 248 249 @Schema(description = "Alternative codes for this collection.") 250 private List<AlternativeCode> alternativeCodes = new ArrayList<>(); 251 252 @Schema( 253 description = "A list of comments associated with this collection.", 254 accessMode = Schema.AccessMode.READ_ONLY) 255 private List<Comment> comments = new ArrayList<>(); 256 257 @Schema( 258 description = "Mapping of a GRSciColl collection to occurrence records", 259 accessMode = Schema.AccessMode.READ_ONLY) 260 private List<OccurrenceMapping> occurrenceMappings = new ArrayList<>(); 261 262 @Schema(description = "A collection record that replaces this collection.") 263 private UUID replacedBy; 264 265 @Schema(description = "The primary source of this collection record.") 266 private MasterSourceType masterSource; 267 268 @Schema( 269 description = 270 "Information to assist the synchronization of the master source " 271 + "record with the record in the GBIF registry.") 272 private MasterSourceMetadata masterSourceMetadata; 273 274 @Setter 275 @Getter 276 @Schema(description = "An organizational department managing the collection.") 277 @Sourceable(masterSources = MasterSourceType.IH) 278 private String department; 279 280 @Setter 281 @Getter 282 @Schema(description = "An organizational division managing the collection.") 283 @Sourceable(masterSources = MasterSourceType.IH) 284 private String division; 285 286 @Schema(description = "Whether the collection is shown on the NHC portal.") 287 private Boolean displayOnNHCPortal; 288 289 @Setter 290 @Getter 291 @Schema(description = "An estimate of the number of occurrences linked to the institution.") 292 private Integer occurrenceCount; 293 294 @Setter 295 @Getter 296 @Schema(description = "An estimate of the number of type specimens linked to the institution.") 297 private Integer typeSpecimenCount; 298 299 @Schema( 300 description = 301 "URI to the image to be featured on the collection page, this image should be associated with a license.") 302 private URI featuredImageUrl; 303 304 @Schema( 305 description = "The license associated with the image to be featured on the collection page.") 306 private License featuredImageLicense; 307 308 @Setter 309 @Schema( 310 description = 311 "Temporal scope or focus of the collection. This free text field can be used to describe both the collection " 312 + "date ranges as well as the geological time group(s) of the collection objects in the context of " 313 + "paleontological collections.") 314 private String temporalCoverage; 315 316 @Schema( 317 description = " Information about ownership, attribution, etc. of the featured image. This value with " 318 + "be used to generate a suggested citation of the image." 319 ) 320 private String featuredImageAttribution; 321 322 /** List of alternative identifiers: UUIDs, external system identifiers, LSIDs, etc.. */ 323 @Override 324 public List<Identifier> getIdentifiers() { 325 return identifiers; 326 } 327 328 @Override 329 public void setIdentifiers(List<Identifier> identifiers) { 330 this.identifiers = identifiers; 331 } 332 333 /** (Meta)Tags or labels. */ 334 @Valid 335 @Override 336 public List<Tag> getTags() { 337 return tags; 338 } 339 340 @Override 341 public void setTags(List<Tag> tags) { 342 this.tags = tags; 343 } 344 345 /** GBIF Unique identifier of this collection. */ 346 @Override 347 public UUID getKey() { 348 return key; 349 } 350 351 @Override 352 public void setKey(UUID key) { 353 this.key = key; 354 } 355 356 /** Collection code: identifies a collection at the owner's location. */ 357 @NotNull(groups = PrePersist.class) 358 @Override 359 public String getCode() { 360 return code; 361 } 362 363 @Override 364 public void setCode(String code) { 365 this.code = code; 366 } 367 368 /** Descriptive name of a collection. */ 369 @NotNull 370 @Override 371 public String getName() { 372 return name; 373 } 374 375 @Override 376 public void setName(String name) { 377 this.name = name; 378 } 379 380 /** Textual description/summary of the contents of a collection. */ 381 @Size(min = 1) 382 @Override 383 public String getDescription() { 384 return description; 385 } 386 387 @Override 388 public void setDescription(String description) { 389 this.description = description; 390 } 391 392 /** Is this collection currently active/maintained. */ 393 @Override 394 public boolean isActive() { 395 return active; 396 } 397 398 @Override 399 public void setActive(boolean active) { 400 this.active = active; 401 } 402 403 @Override 404 public List<String> getEmail() { 405 return email; 406 } 407 408 @Override 409 public void setEmail(List<String> email) { 410 this.email = email; 411 } 412 413 @Override 414 public List<String> getPhone() { 415 return phone; 416 } 417 418 @Override 419 public void setPhone(List<String> phone) { 420 this.phone = phone; 421 } 422 423 /** URL containing information about a collection. */ 424 @HttpURI 425 @Nullable 426 public URI getHomepage() { 427 return homepage; 428 } 429 430 /** URI that exposes data about the catalog associated to a collection. */ 431 @Nullable 432 public List<URI> getCatalogUrls() { 433 return catalogUrls; 434 } 435 436 /** Machine consumable endpoint of information about a collection. */ 437 @Nullable 438 public List<URI> getApiUrls() { 439 return apiUrls; 440 } 441 442 /** Address used to send/receive physical mail. */ 443 @Nullable 444 @Valid 445 @Override 446 public Address getMailingAddress() { 447 return mailingAddress; 448 } 449 450 @Override 451 public void setMailingAddress(Address mailingAddress) { 452 this.mailingAddress = mailingAddress; 453 } 454 455 /** Address where this collection is situated. */ 456 @Nullable 457 @Valid 458 @Override 459 public Address getAddress() { 460 return address; 461 } 462 463 @Override 464 public void setAddress(Address address) { 465 this.address = address; 466 } 467 468 @Override 469 public String getCreatedBy() { 470 return createdBy; 471 } 472 473 @Override 474 public void setCreatedBy(String createdBy) { 475 this.createdBy = createdBy; 476 } 477 478 @Override 479 public String getModifiedBy() { 480 return modifiedBy; 481 } 482 483 @Override 484 public void setModifiedBy(String modifiedBy) { 485 this.modifiedBy = modifiedBy; 486 } 487 488 @Override 489 public Date getCreated() { 490 return created; 491 } 492 493 @Override 494 public void setCreated(Date created) { 495 this.created = created; 496 } 497 498 @Override 499 public Date getModified() { 500 return modified; 501 } 502 503 @Override 504 public void setModified(Date modified) { 505 this.modified = modified; 506 } 507 508 @Override 509 public Date getDeleted() { 510 return deleted; 511 } 512 513 @Override 514 public void setDeleted(Date deleted) { 515 this.deleted = deleted; 516 } 517 518 @Valid 519 @Override 520 public List<Contact> getContactPersons() { 521 return contactPersons; 522 } 523 524 @Override 525 public void setContactPersons(List<Contact> contactPersons) { 526 this.contactPersons = contactPersons; 527 } 528 529 @Valid 530 @Override 531 public @NotNull List<MachineTag> getMachineTags() { 532 return machineTags; 533 } 534 535 @Override 536 public void setMachineTags(List<MachineTag> machineTags) { 537 this.machineTags = machineTags; 538 } 539 540 @Override 541 public void addMachineTag(MachineTag machineTag) { 542 machineTags.add(machineTag); 543 } 544 545 /** Alternative codes for a collection. */ 546 @Override 547 public List<AlternativeCode> getAlternativeCodes() { 548 return alternativeCodes; 549 } 550 551 @Override 552 public void setAlternativeCodes(List<AlternativeCode> alternativeCodes) { 553 this.alternativeCodes = alternativeCodes; 554 } 555 556 @Override 557 public @NotNull List<Comment> getComments() { 558 return comments; 559 } 560 561 @Override 562 public void setComments(List<Comment> comments) { 563 this.comments = comments; 564 } 565 566 @Override 567 public @NotNull List<OccurrenceMapping> getOccurrenceMappings() { 568 return occurrenceMappings; 569 } 570 571 @Override 572 public void setOccurrenceMappings(List<OccurrenceMapping> occurrenceMappings) { 573 this.occurrenceMappings = occurrenceMappings; 574 } 575 576 @Override 577 public UUID getReplacedBy() { 578 return replacedBy; 579 } 580 581 @Override 582 public void setReplacedBy(UUID replacedBy) { 583 this.replacedBy = replacedBy; 584 } 585 586 @Override 587 public MasterSourceType getMasterSource() { 588 return masterSource; 589 } 590 591 @Override 592 public void setMasterSource(MasterSourceType masterSource) { 593 this.masterSource = masterSource; 594 } 595 596 @Override 597 public MasterSourceMetadata getMasterSourceMetadata() { 598 return masterSourceMetadata; 599 } 600 601 @Override 602 public void setMasterSourceMetadata(MasterSourceMetadata masterSourceMetadata) { 603 this.masterSourceMetadata = masterSourceMetadata; 604 } 605 606 @Override 607 public Boolean getDisplayOnNHCPortal() { 608 return displayOnNHCPortal; 609 } 610 611 @Override 612 public void setDisplayOnNHCPortal(Boolean displayOnNHCPortal) { 613 this.displayOnNHCPortal = displayOnNHCPortal; 614 } 615 616 @HttpURI 617 @Nullable 618 @Override 619 public URI getFeaturedImageUrl() { 620 return featuredImageUrl; 621 } 622 623 @Override 624 public void setFeaturedImageUrl(URI featuredImageUrl) { 625 this.featuredImageUrl = featuredImageUrl; 626 } 627 628 @Nullable 629 @Override 630 public License getFeaturedImageLicense() { 631 return featuredImageLicense; 632 } 633 634 @Override 635 public void setFeaturedImageLicense(License featuredImageLicense) { 636 this.featuredImageLicense = featuredImageLicense; 637 } 638 639 @Nullable 640 public String getTemporalCoverage() { 641 return temporalCoverage; 642 } 643 644 @Hidden 645 @JsonIgnore 646 public String getCountry() { 647 if (address != null && address.getCountry() != null) { 648 return address.getCountry().getIso2LetterCode(); 649 } else if (mailingAddress != null && mailingAddress.getCountry() != null) { 650 return mailingAddress.getCountry().getIso2LetterCode(); 651 } 652 return null; 653 } 654 655 @Hidden 656 @JsonIgnore 657 public String getCity() { 658 if (address != null && address.getCity() != null) { 659 return address.getCity(); 660 } else if (mailingAddress != null && mailingAddress.getCity() != null) { 661 return mailingAddress.getCity(); 662 } 663 return null; 664 } 665 666 @Hidden 667 @JsonIgnore 668 public String getProvince() { 669 if (address != null && address.getProvince() != null) { 670 return address.getProvince(); 671 } else if (mailingAddress != null && mailingAddress.getProvince() != null) { 672 return mailingAddress.getProvince(); 673 } 674 return null; 675 } 676 677 @Nullable 678 @Override 679 public String getFeaturedImageAttribution() { 680 return featuredImageAttribution; 681 } 682 683 @Override 684 public void setFeaturedImageAttribution(String featuredImageAttribution) { 685 this.featuredImageAttribution = featuredImageAttribution; 686 } 687 688 @Override 689 public boolean lenientEquals(Collection other) { 690 if (this == other) { 691 return true; 692 } 693 return active == other.active 694 && personalCollection == other.personalCollection 695 && Objects.equals(key, other.key) 696 && Objects.equals(code, other.code) 697 && Objects.equals(name, other.name) 698 && Objects.equals(description, other.description) 699 && Objects.equals(contentTypes, other.contentTypes) 700 && Objects.equals(doi, other.doi) 701 && Objects.equals(email, other.email) 702 && Objects.equals(phone, other.phone) 703 && Objects.equals(homepage, other.homepage) 704 && Objects.equals(catalogUrls, other.catalogUrls) 705 && Objects.equals(apiUrls, other.apiUrls) 706 && Objects.equals(preservationTypes, other.preservationTypes) 707 && Objects.equals(accessionStatus, other.accessionStatus) 708 && Objects.equals(institutionKey, other.institutionKey) 709 && LenientEqualsUtils.lenientEquals(mailingAddress, other.mailingAddress) 710 && LenientEqualsUtils.lenientEquals(address, other.address) 711 && Objects.equals(deleted, other.deleted) 712 && Objects.equals(numberSpecimens, other.numberSpecimens) 713 && Objects.equals(taxonomicCoverage, other.taxonomicCoverage) 714 && Objects.equals(geographicCoverage, other.geographicCoverage) 715 && Objects.equals(notes, other.notes) 716 && Objects.equals(incorporatedCollections, other.incorporatedCollections) 717 && Objects.equals(alternativeCodes, other.alternativeCodes) 718 && Objects.equals(comments, other.comments) 719 && Objects.equals(occurrenceMappings, other.occurrenceMappings) 720 && Objects.equals(replacedBy, other.replacedBy) 721 && Objects.equals(masterSource, other.masterSource) 722 && Objects.equals(masterSourceMetadata, other.masterSourceMetadata) 723 && Objects.equals(division, other.division) 724 && Objects.equals(department, other.department) 725 && Objects.equals(displayOnNHCPortal, other.displayOnNHCPortal) 726 && Objects.equals(featuredImageUrl, other.featuredImageUrl) 727 && Objects.equals(featuredImageLicense, other.featuredImageLicense) 728 && Objects.equals(temporalCoverage, other.temporalCoverage) 729 && Objects.equals(featuredImageAttribution, other.featuredImageAttribution); 730 } 731}