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}