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  @Sourceable(masterSources = MasterSourceType.GBIF_REGISTRY)
315  private String temporalCoverage;
316
317  @Schema(
318    description = " Information about ownership, attribution, etc. of the featured image. This value with "
319      + "be used to generate a suggested citation of the image."
320  )
321  private String featuredImageAttribution;
322
323  /** List of alternative identifiers: UUIDs, external system identifiers, LSIDs, etc.. */
324  @Override
325  public List<Identifier> getIdentifiers() {
326    return identifiers;
327  }
328
329  @Override
330  public void setIdentifiers(List<Identifier> identifiers) {
331    this.identifiers = identifiers;
332  }
333
334  /** (Meta)Tags or labels. */
335  @Valid
336  @Override
337  public List<Tag> getTags() {
338    return tags;
339  }
340
341  @Override
342  public void setTags(List<Tag> tags) {
343    this.tags = tags;
344  }
345
346  /** GBIF Unique identifier of this collection. */
347  @Override
348  public UUID getKey() {
349    return key;
350  }
351
352  @Override
353  public void setKey(UUID key) {
354    this.key = key;
355  }
356
357  /** Collection code: identifies a collection at the owner's location. */
358  @NotNull(groups = PrePersist.class)
359  @Override
360  public String getCode() {
361    return code;
362  }
363
364  @Override
365  public void setCode(String code) {
366    this.code = code;
367  }
368
369  /** Descriptive name of a collection. */
370  @NotNull
371  @Override
372  public String getName() {
373    return name;
374  }
375
376  @Override
377  public void setName(String name) {
378    this.name = name;
379  }
380
381  /** Textual description/summary of the contents of a collection. */
382  @Size(min = 1)
383  @Override
384  public String getDescription() {
385    return description;
386  }
387
388  @Override
389  public void setDescription(String description) {
390    this.description = description;
391  }
392
393  /** Is this collection currently active/maintained. */
394  @Override
395  public boolean isActive() {
396    return active;
397  }
398
399  @Override
400  public void setActive(boolean active) {
401    this.active = active;
402  }
403
404  @Override
405  public List<String> getEmail() {
406    return email;
407  }
408
409  @Override
410  public void setEmail(List<String> email) {
411    this.email = email;
412  }
413
414  @Override
415  public List<String> getPhone() {
416    return phone;
417  }
418
419  @Override
420  public void setPhone(List<String> phone) {
421    this.phone = phone;
422  }
423
424  /** URL containing information about a collection. */
425  @HttpURI
426  @Nullable
427  public URI getHomepage() {
428    return homepage;
429  }
430
431  /** URI that exposes data about the catalog associated to a collection. */
432  @Nullable
433  public List<URI> getCatalogUrls() {
434    return catalogUrls;
435  }
436
437  /** Machine consumable endpoint of information about a collection. */
438  @Nullable
439  public List<URI> getApiUrls() {
440    return apiUrls;
441  }
442
443  /** Address used to send/receive physical mail. */
444  @Nullable
445  @Valid
446  @Override
447  public Address getMailingAddress() {
448    return mailingAddress;
449  }
450
451  @Override
452  public void setMailingAddress(Address mailingAddress) {
453    this.mailingAddress = mailingAddress;
454  }
455
456  /** Address where this collection is situated. */
457  @Nullable
458  @Valid
459  @Override
460  public Address getAddress() {
461    return address;
462  }
463
464  @Override
465  public void setAddress(Address address) {
466    this.address = address;
467  }
468
469  @Override
470  public String getCreatedBy() {
471    return createdBy;
472  }
473
474  @Override
475  public void setCreatedBy(String createdBy) {
476    this.createdBy = createdBy;
477  }
478
479  @Override
480  public String getModifiedBy() {
481    return modifiedBy;
482  }
483
484  @Override
485  public void setModifiedBy(String modifiedBy) {
486    this.modifiedBy = modifiedBy;
487  }
488
489  @Override
490  public Date getCreated() {
491    return created;
492  }
493
494  @Override
495  public void setCreated(Date created) {
496    this.created = created;
497  }
498
499  @Override
500  public Date getModified() {
501    return modified;
502  }
503
504  @Override
505  public void setModified(Date modified) {
506    this.modified = modified;
507  }
508
509  @Override
510  public Date getDeleted() {
511    return deleted;
512  }
513
514  @Override
515  public void setDeleted(Date deleted) {
516    this.deleted = deleted;
517  }
518
519  @Valid
520  @Override
521  public List<Contact> getContactPersons() {
522    return contactPersons;
523  }
524
525  @Override
526  public void setContactPersons(List<Contact> contactPersons) {
527    this.contactPersons = contactPersons;
528  }
529
530  @Valid
531  @Override
532  public @NotNull List<MachineTag> getMachineTags() {
533    return machineTags;
534  }
535
536  @Override
537  public void setMachineTags(List<MachineTag> machineTags) {
538    this.machineTags = machineTags;
539  }
540
541  @Override
542  public void addMachineTag(MachineTag machineTag) {
543    machineTags.add(machineTag);
544  }
545
546  /** Alternative codes for a collection. */
547  @Override
548  public List<AlternativeCode> getAlternativeCodes() {
549    return alternativeCodes;
550  }
551
552  @Override
553  public void setAlternativeCodes(List<AlternativeCode> alternativeCodes) {
554    this.alternativeCodes = alternativeCodes;
555  }
556
557  @Override
558  public @NotNull List<Comment> getComments() {
559    return comments;
560  }
561
562  @Override
563  public void setComments(List<Comment> comments) {
564    this.comments = comments;
565  }
566
567  @Override
568  public @NotNull List<OccurrenceMapping> getOccurrenceMappings() {
569    return occurrenceMappings;
570  }
571
572  @Override
573  public void setOccurrenceMappings(List<OccurrenceMapping> occurrenceMappings) {
574    this.occurrenceMappings = occurrenceMappings;
575  }
576
577  @Override
578  public UUID getReplacedBy() {
579    return replacedBy;
580  }
581
582  @Override
583  public void setReplacedBy(UUID replacedBy) {
584    this.replacedBy = replacedBy;
585  }
586
587  @Override
588  public MasterSourceType getMasterSource() {
589    return masterSource;
590  }
591
592  @Override
593  public void setMasterSource(MasterSourceType masterSource) {
594    this.masterSource = masterSource;
595  }
596
597  @Override
598  public MasterSourceMetadata getMasterSourceMetadata() {
599    return masterSourceMetadata;
600  }
601
602  @Override
603  public void setMasterSourceMetadata(MasterSourceMetadata masterSourceMetadata) {
604    this.masterSourceMetadata = masterSourceMetadata;
605  }
606
607  @Override
608  public Boolean getDisplayOnNHCPortal() {
609    return displayOnNHCPortal;
610  }
611
612  @Override
613  public void setDisplayOnNHCPortal(Boolean displayOnNHCPortal) {
614    this.displayOnNHCPortal = displayOnNHCPortal;
615  }
616
617  @HttpURI
618  @Nullable
619  @Override
620  public URI getFeaturedImageUrl() {
621    return featuredImageUrl;
622  }
623
624  @Override
625  public void setFeaturedImageUrl(URI featuredImageUrl) {
626    this.featuredImageUrl = featuredImageUrl;
627  }
628
629  @Nullable
630  @Override
631  public License getFeaturedImageLicense() {
632    return featuredImageLicense;
633  }
634
635  @Override
636  public void setFeaturedImageLicense(License featuredImageLicense) {
637    this.featuredImageLicense = featuredImageLicense;
638  }
639
640  @Nullable
641  public String getTemporalCoverage() {
642    return temporalCoverage;
643  }
644
645  @Hidden
646  @JsonIgnore
647  public String getCountry() {
648    if (address != null && address.getCountry() != null) {
649      return address.getCountry().getIso2LetterCode();
650    } else if (mailingAddress != null && mailingAddress.getCountry() != null) {
651      return mailingAddress.getCountry().getIso2LetterCode();
652    }
653    return null;
654  }
655
656  @Hidden
657  @JsonIgnore
658  public String getCity() {
659    if (address != null && address.getCity() != null) {
660      return address.getCity();
661    } else if (mailingAddress != null && mailingAddress.getCity() != null) {
662      return mailingAddress.getCity();
663    }
664    return null;
665  }
666
667  @Hidden
668  @JsonIgnore
669  public String getProvince() {
670    if (address != null && address.getProvince() != null) {
671      return address.getProvince();
672    } else if (mailingAddress != null && mailingAddress.getProvince() != null) {
673      return mailingAddress.getProvince();
674    }
675    return null;
676  }
677
678  @Nullable
679  @Override
680  public String getFeaturedImageAttribution() {
681    return featuredImageAttribution;
682  }
683
684  @Override
685  public void setFeaturedImageAttribution(String featuredImageAttribution) {
686    this.featuredImageAttribution = featuredImageAttribution;
687  }
688
689  @Override
690  public boolean lenientEquals(Collection other) {
691    if (this == other) {
692      return true;
693    }
694    return active == other.active
695        && personalCollection == other.personalCollection
696        && Objects.equals(key, other.key)
697        && Objects.equals(code, other.code)
698        && Objects.equals(name, other.name)
699        && Objects.equals(description, other.description)
700        && Objects.equals(contentTypes, other.contentTypes)
701        && Objects.equals(doi, other.doi)
702        && Objects.equals(email, other.email)
703        && Objects.equals(phone, other.phone)
704        && Objects.equals(homepage, other.homepage)
705        && Objects.equals(catalogUrls, other.catalogUrls)
706        && Objects.equals(apiUrls, other.apiUrls)
707        && Objects.equals(preservationTypes, other.preservationTypes)
708        && Objects.equals(accessionStatus, other.accessionStatus)
709        && Objects.equals(institutionKey, other.institutionKey)
710        && LenientEqualsUtils.lenientEquals(mailingAddress, other.mailingAddress)
711        && LenientEqualsUtils.lenientEquals(address, other.address)
712        && Objects.equals(deleted, other.deleted)
713        && Objects.equals(numberSpecimens, other.numberSpecimens)
714        && Objects.equals(taxonomicCoverage, other.taxonomicCoverage)
715        && Objects.equals(geographicCoverage, other.geographicCoverage)
716        && Objects.equals(notes, other.notes)
717        && Objects.equals(incorporatedCollections, other.incorporatedCollections)
718        && Objects.equals(alternativeCodes, other.alternativeCodes)
719        && Objects.equals(comments, other.comments)
720        && Objects.equals(occurrenceMappings, other.occurrenceMappings)
721        && Objects.equals(replacedBy, other.replacedBy)
722        && Objects.equals(masterSource, other.masterSource)
723        && Objects.equals(masterSourceMetadata, other.masterSourceMetadata)
724        && Objects.equals(division, other.division)
725        && Objects.equals(department, other.department)
726        && Objects.equals(displayOnNHCPortal, other.displayOnNHCPortal)
727        && Objects.equals(featuredImageUrl, other.featuredImageUrl)
728        && Objects.equals(featuredImageLicense, other.featuredImageLicense)
729        && Objects.equals(temporalCoverage, other.temporalCoverage)
730        && Objects.equals(featuredImageAttribution, other.featuredImageAttribution);
731  }
732}