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