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.registry;
015
016import com.fasterxml.jackson.annotation.JsonRawValue;
017
018import com.fasterxml.jackson.core.JsonParser;
019import com.fasterxml.jackson.databind.DeserializationContext;
020import com.fasterxml.jackson.databind.JsonDeserializer;
021
022import com.fasterxml.jackson.databind.JsonNode;
023
024import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
025
026import lombok.Data;
027import lombok.Getter;
028import lombok.NoArgsConstructor;
029import lombok.Setter;
030
031import org.gbif.api.annotation.Experimental;
032import org.gbif.api.model.common.DOI;
033import org.gbif.api.model.registry.eml.Collection;
034import org.gbif.api.model.registry.eml.DataDescription;
035import org.gbif.api.model.registry.eml.KeywordCollection;
036import org.gbif.api.model.registry.eml.Project;
037import org.gbif.api.model.registry.eml.SamplingDescription;
038import org.gbif.api.model.registry.eml.TaxonomicCoverages;
039import org.gbif.api.model.registry.eml.curatorial.CuratorialUnitComposite;
040import org.gbif.api.model.registry.eml.geospatial.GeospatialCoverage;
041import org.gbif.api.model.registry.eml.temporal.TemporalCoverage;
042import org.gbif.api.util.HttpURI;
043import org.gbif.api.vocabulary.Country;
044import org.gbif.api.vocabulary.DatasetSubtype;
045import org.gbif.api.vocabulary.DatasetType;
046import org.gbif.api.vocabulary.Language;
047import org.gbif.api.vocabulary.License;
048import org.gbif.api.vocabulary.MaintenanceUpdateFrequency;
049
050import java.io.IOException;
051import java.net.URI;
052import java.util.ArrayList;
053import java.util.Date;
054import java.util.HashSet;
055import java.util.List;
056import java.util.Objects;
057import java.util.Set;
058import java.util.StringJoiner;
059import java.util.UUID;
060
061import jakarta.annotation.Nullable;
062import jakarta.validation.Valid;
063import jakarta.validation.constraints.Min;
064import jakarta.validation.constraints.NotNull;
065import jakarta.validation.constraints.Null;
066import jakarta.validation.constraints.Size;
067
068import io.swagger.v3.oas.annotations.media.Schema;
069
070/**
071 * A GBIF dataset which provides occurrence data, checklist data, sampling event data or metadata.
072 * This Dataset class is covering all the GBIF metadata profile v1.3, but only a few properties are kept in the
073 * database table:
074 * <ul>
075 * <li>key</li>
076 * <li>parentDatasetKey</li>
077 * <li>duplicateOfDatasetKey</li>
078 * <li>version</li>
079 * <li>installationKey</li>
080 * <li>publishingOrganizationKey</li>
081 * <li>publishingOrganizationName</li>
082 * <li>networkKeys</li>
083 * <li>license</li>
084 * <li>maintenanceUpdateFrequency</li>
085 * <li>external</li>
086 * <li>numConstituents</li>
087 * <li>type</li>
088 * <li>subtype</li>
089 * <li>title</li>
090 * <li>alias</li>
091 * <li>abbreviation</li>
092 * <li>description</li>
093 * <li>language</li>
094 * <li>homepage</li>
095 * <li>logoUrl</li>
096 * <li>citation</li>
097 * <li>rights</li>
098 * <li>lockedForAutoUpdate</li>
099 * <li>createdBy</li>
100 * <li>modifiedBy</li>
101 * <li>created</li>
102 * <li>modified</li>
103 * <li>deleted</li>
104 * </ul>
105 *
106 * @see <a href="http://rs.gbif.org/schema/eml-gbif-profile/dev/eml.xsd">GBIF EML Profile XML Schema</a>
107 */
108@SuppressWarnings({"unused", "LombokSetterMayBeUsed", "LombokGetterMayBeUsed"})
109public class Dataset
110    implements NetworkEntity,
111    Contactable,
112    Endpointable,
113    MachineTaggable,
114    Taggable,
115    Identifiable,
116    Commentable,
117    LenientEquals<Dataset> {
118
119  @Schema(
120    description = "Unique GBIF key for the dataset. This is used in the" +
121      "GBIF API, but outside GBIF it is best to refer to a dataset by its DOI.",
122    accessMode = Schema.AccessMode.READ_ONLY
123  )
124  private UUID key;
125
126  @Schema(
127    description = "If set, this dataset is a sub-dataset of the parent."
128  )
129  private UUID parentDatasetKey;
130
131  @Schema(
132    description = "A dataset of which this dataset is a duplicate. Typically, " +
133      "this means this dataset is an old version of the duplicated dataset, " +
134      "which has replaced this dataset. Therefore **this link is usually found " +
135      "on deleted datasets**."
136  )
137  private UUID duplicateOfDatasetKey;
138
139  @Schema(
140    description = "The installation providing access to the source dataset.\n\n" +
141      "*(NB Not required for updates.)*"
142  )
143  private UUID installationKey;
144
145  @Schema(
146    description = "The publishing organization publishing this dataset.\n\n" +
147      "*(NB Not required for updates.)*"
148  )
149  private UUID publishingOrganizationKey;
150
151  @Schema(
152      description = "The publishing organization name.\n\n" +
153          "*(NB Not required for updates.)*"
154  )
155  private String publishingOrganizationName;
156
157  @Schema(
158    description = "A list of GBIF Networks to which this dataset belongs."
159  )
160  private List<UUID> networkKeys;
161
162  @Schema(
163    description = "The primary Digital Object Identifier (DOI) for this dataset.",
164    implementation = String.class,
165    pattern = "(10(?:\\.[0-9]+)+)" + "/(.+)"
166  )
167  private DOI doi;
168
169  @Schema(
170    description = "The version of the published dataset."
171  )
172  private String version;
173
174  @Schema(
175    description = "Not currently used."
176  )
177  private boolean external;
178
179  @Schema(
180    description = "If set, the number of sub-datasets of this parent dataset."
181  )
182  private int numConstituents;
183
184  @Schema(
185    description = "The primary type of the dataset.\n\n" +
186      "*(NB Not required for updates.)*"
187  )
188  private DatasetType type;
189
190  @Schema(
191    description = "The sub-type of the dataset."
192  )
193  private DatasetSubtype subtype;
194
195  @Schema(
196      description = "Concise name of the dataset."
197  )
198  private String shortName;
199
200  @Schema(
201    description = "The title of the dataset.\n\n" +
202      "*(NB Not required for updates.)*"
203  )
204  private String title;
205
206  @Schema(
207    description = "An alias for this dataset. Rarely used."
208  )
209  private String alias;
210
211  @Schema(
212    description = "An abbreviation for this dataset. Rarely used."
213  )
214  private String abbreviation;
215
216  @Schema(
217    description = "A description of the dataset."
218  )
219  private String description;
220
221  @Schema(
222    description = "The language of the dataset metadata.\n\n" +
223      "*(NB Not required for updates.)*"
224  )
225  private Language language = Language.ENGLISH; // sensible default as it is not null
226
227  @Schema(
228    description = "A homepage with further details on the dataset."
229  )
230  private URI homepage;
231
232  @Schema(
233    description = "A logo for the dataset, accessible over HTTP."
234  )
235  private URI logoUrl;
236
237  @Schema(
238    description = "The citation recommended by GBIF for use when citing this dataset."
239  )
240  private Citation citation = new Citation();
241
242  @Schema(
243    description = "Contacts use to generate a citation."
244  )
245  private List<CitationContact> contactsCitation = new ArrayList<>();
246
247  @Schema(
248    description = "Intellectual property rights applied to this dataset.\n\n" +
249      "*Rarely used, see `license` instead.*"
250  )
251  private String rights;
252
253  @Schema(
254    description = "If true, any new or updated metadata is ignored.\n\n" +
255      "This is generally used when the publisher has technical problems or " +
256      "limitations with their publication system.",
257    accessMode = Schema.AccessMode.READ_ONLY
258  )
259  private boolean lockedForAutoUpdate;
260
261  @Schema(
262    description = "The GBIF username of the creator of the dataset.",
263    accessMode = Schema.AccessMode.READ_ONLY
264  )
265  private String createdBy;
266
267  @Schema(
268    description = "The GBIF username of the last user to modify the dataset.",
269    accessMode = Schema.AccessMode.READ_ONLY
270  )
271  private String modifiedBy;
272
273  @Schema(
274    description = "Timestamp of when the dataset was created.",
275    accessMode = Schema.AccessMode.READ_ONLY
276  )
277  private Date created;
278
279  @Schema(
280    description = "Timestamp of when the dataset was modified.",
281    accessMode = Schema.AccessMode.READ_ONLY
282  )
283  private Date modified;
284
285  @Schema(
286    description = "If present, the dataset was deleted at this time. " +
287      "It is possible for it to be restored in the future.",
288    accessMode = Schema.AccessMode.READ_ONLY
289  )
290  private Date deleted;
291
292  @Schema(
293    description = "A list of contacts associated with this dataset.",
294    accessMode = Schema.AccessMode.READ_ONLY
295  )
296  private List<Contact> contacts = new ArrayList<>();
297
298  @Schema(
299    description = "A list of endpoints associated with this dataset.",
300    accessMode = Schema.AccessMode.READ_ONLY
301  )
302  private List<Endpoint> endpoints = new ArrayList<>();
303
304  @Schema(
305    description = "A list of machine tags associated with this dataset.",
306    accessMode = Schema.AccessMode.READ_ONLY
307  )
308  private List<MachineTag> machineTags = new ArrayList<>();
309
310  @Schema(
311    description = "A list of tags associated with this dataset.",
312    accessMode = Schema.AccessMode.READ_ONLY
313  )
314  private List<Tag> tags = new ArrayList<>();
315
316  @Schema(
317    description = "A list of identifiers associated with this dataset.",
318    accessMode = Schema.AccessMode.READ_ONLY
319  )
320  private List<Identifier> identifiers = new ArrayList<>();
321
322  @Schema(
323    description = "A list of comments associated with this dataset.",
324    accessMode = Schema.AccessMode.READ_ONLY
325  )
326  private List<Comment> comments = new ArrayList<>();
327
328  // EML specific properties which are not persisted on the dataset table!
329  @Schema(
330    description = "Citations retrieved from this dataset's metadata documents.",
331    accessMode = Schema.AccessMode.READ_ONLY
332  )
333  private List<Citation> bibliographicCitations = new ArrayList<>();
334
335  @Schema(
336    description = "Curatorial unit information retrieved from this dataset's metadata documents.",
337    accessMode = Schema.AccessMode.READ_ONLY
338  )
339  private List<CuratorialUnitComposite> curatorialUnits = new ArrayList<>();
340
341  @Schema(
342    description = "Taxonomic coverage information retrieved from this dataset's metadata documents.",
343    accessMode = Schema.AccessMode.READ_ONLY
344  )
345  private List<TaxonomicCoverages> taxonomicCoverages = new ArrayList<>();
346
347  @Schema(
348    description = "Geographic coverage description retrieved from this dataset's metadata documents.",
349    accessMode = Schema.AccessMode.READ_ONLY
350  )
351  private String geographicCoverageDescription;
352
353  @Schema(
354    description = "Geospatial coverage information retrieved from this dataset's metadata documents.",
355    accessMode = Schema.AccessMode.READ_ONLY
356  )
357  private List<GeospatialCoverage> geographicCoverages = new ArrayList<>();
358
359  @Schema(
360    description = "Temporal coverage information retrieved from this dataset's metadata documents.",
361    accessMode = Schema.AccessMode.READ_ONLY
362  )
363  private List<TemporalCoverage> temporalCoverages = new ArrayList<>();
364
365  @Schema(
366    description = "Keyword collections retrieved from this dataset's metadata documents.",
367    accessMode = Schema.AccessMode.READ_ONLY
368  )
369  private List<KeywordCollection> keywordCollections = new ArrayList<>();
370
371  @Schema(
372    description = "Project information retrieved from this dataset's metadata documents.",
373    accessMode = Schema.AccessMode.READ_ONLY
374  )
375  private Project project;
376
377  @Schema(
378    description = "Sampling description retrieved from this dataset's metadata documents.",
379    accessMode = Schema.AccessMode.READ_ONLY
380  )
381  private SamplingDescription samplingDescription;
382
383  @Schema(
384    description = "Country coverage information retrieved from this dataset's metadata documents.",
385    accessMode = Schema.AccessMode.READ_ONLY
386  )
387  private Set<Country> countryCoverage = new HashSet<>();
388
389  @Schema(
390    description = "Collection information retrieved from this dataset's metadata documents.",
391    accessMode = Schema.AccessMode.READ_ONLY
392  )
393  private List<Collection> collections = new ArrayList<>();
394
395  @Schema(
396    description = "Data description information retrieved from this dataset's metadata documents.",
397    accessMode = Schema.AccessMode.READ_ONLY
398  )
399  private List<DataDescription> dataDescriptions = new ArrayList<>();
400
401  @Schema(
402    description = "Data language information retrieved from this dataset's metadata documents.",
403    accessMode = Schema.AccessMode.READ_ONLY
404  )
405  private Language dataLanguage;
406
407  @Schema(
408    description = "Purpose information retrieved from this dataset's metadata documents.",
409    accessMode = Schema.AccessMode.READ_ONLY
410  )
411  private String purpose;
412
413  @Schema(
414      description = "An overview of the background and context for the dataset.",
415      accessMode = Schema.AccessMode.READ_ONLY
416  )
417  private String introduction;
418
419  @Schema(
420      description = "A high level overview of interpretation, structure, and content of the dataset.",
421      accessMode = Schema.AccessMode.READ_ONLY
422  )
423  private String gettingStarted;
424
425  @Schema(
426      description = "Text that acknowledges funders and other key contributors.",
427      accessMode = Schema.AccessMode.READ_ONLY
428  )
429  private String acknowledgements;
430
431  @Schema(
432    description = "Additional information retrieved from this dataset's metadata documents.",
433    accessMode = Schema.AccessMode.READ_ONLY
434  )
435  private String additionalInfo;
436
437  @Schema(
438    description = "The publication date retrieved from this dataset's metadata documents.",
439    accessMode = Schema.AccessMode.READ_ONLY
440  )
441  private Date pubDate;
442
443  @Schema(
444    description = "The maintenance update frequency retrieved from this dataset's metadata documents.",
445    accessMode = Schema.AccessMode.READ_ONLY
446  )
447  private MaintenanceUpdateFrequency maintenanceUpdateFrequency;
448
449  @Schema(
450    description = "The maintenance description retrieved from this dataset's metadata documents.",
451    accessMode = Schema.AccessMode.READ_ONLY
452  )
453  private String maintenanceDescription;
454
455  @Schema(
456    description = "The data and metadata license retrieved from this dataset's metadata documents.",
457    accessMode = Schema.AccessMode.READ_ONLY
458  )
459  private License license;
460
461  @Schema(
462    description = "Basic metadata of the Darwin Core Archive (DwC-A) associated with this dataset.",
463    accessMode = Schema.AccessMode.READ_ONLY
464  )
465  private DwcA dwca;
466
467  @Schema(
468    description = "Category of this dataset.",
469    accessMode = Schema.AccessMode.READ_ONLY
470  )
471  private Set<String> category;
472
473  @Schema(
474    description =
475        "Approximate record counts for this dataset, sourced from the dataset search index. "
476            + "Values are snapshots taken at index time and may lag live occurrence or checklist data. "
477            + "Useful to avoid extra calls for occurrence and name-usage totals; not authoritative.",
478    accessMode = Schema.AccessMode.READ_ONLY
479  )
480  @Nullable
481  @Getter
482  @Setter
483  private ApproximateCounts approximateCounts;
484
485  @Override
486  public UUID getKey() {
487    return key;
488  }
489
490  /**
491   * Persisted in the database table.
492   */
493  @Override
494  public void setKey(UUID key) {
495    this.key = key;
496  }
497
498  @Size(max = 10)
499  @Nullable
500  public String getVersion() {
501    return version;
502  }
503
504  public void setVersion(String version) {
505    this.version = version;
506  }
507
508  @Override
509  public String getTitle() {
510    return title;
511  }
512
513  /**
514   * Persisted in the database table.
515   */
516  @Override
517  public void setTitle(String title) {
518    this.title = title;
519  }
520
521  @Override
522  @Nullable
523  public String getDescription() {
524    return description;
525  }
526
527  /**
528   * Persisted in the database table.
529   */
530  @Override
531  public void setDescription(String description) {
532    this.description = description;
533  }
534
535  @Override
536  public Date getCreated() {
537    return created;
538  }
539
540  /**
541   * Autoassigned in the database table, ignored when persisted.
542   */
543  @Override
544  public void setCreated(Date created) {
545    this.created = created;
546  }
547
548  @Override
549  public Date getModified() {
550    return modified;
551  }
552
553  /**
554   * Persisted in the database table.
555   */
556  @Override
557  public void setModified(Date modified) {
558    this.modified = modified;
559  }
560
561  @Override
562  @Nullable
563  public Date getDeleted() {
564    return deleted;
565  }
566
567  /**
568   * Persisted in the database table.
569   */
570  @Override
571  public void setDeleted(Date deleted) {
572    this.deleted = deleted;
573  }
574
575  @Nullable
576  public UUID getParentDatasetKey() {
577    return parentDatasetKey;
578  }
579
580  /**
581   * Persisted in the database table.
582   */
583  public void setParentDatasetKey(UUID parentDatasetKey) {
584    this.parentDatasetKey = parentDatasetKey;
585  }
586
587  /**
588   * If a dataset is registered with GBIF through more than one place we'll mark all but one as a duplicate by pointing
589   * it to the canonical dataset. That is done using this field. If it is {@code null} then this is not a known
590   * duplicate.
591   */
592  @Nullable
593  public UUID getDuplicateOfDatasetKey() {
594    return duplicateOfDatasetKey;
595  }
596
597  /**
598   * Persisted in the database table.
599   */
600  public void setDuplicateOfDatasetKey(UUID duplicateOfDatasetKey) {
601    this.duplicateOfDatasetKey = duplicateOfDatasetKey;
602  }
603
604  @NotNull
605  public UUID getInstallationKey() {
606    return installationKey;
607  }
608
609  /**
610   * Persisted in the database table.
611   */
612  public void setInstallationKey(UUID installationKey) {
613    this.installationKey = installationKey;
614  }
615
616  @NotNull
617  public UUID getPublishingOrganizationKey() {
618    return publishingOrganizationKey;
619  }
620
621  /**
622   * Persisted in the database table.
623   */
624  public void setPublishingOrganizationKey(UUID publishingOrganizationKey) {
625    this.publishingOrganizationKey = publishingOrganizationKey;
626  }
627
628  @Nullable
629  public String getPublishingOrganizationName() {
630    return publishingOrganizationName;
631  }
632
633  public void setPublishingOrganizationName(String publishingOrganizationName) {
634    this.publishingOrganizationName = publishingOrganizationName;
635  }
636
637  /**
638   * Networks in which this dataset is a constituent.
639   */
640  public List<UUID> getNetworkKeys() {
641    return networkKeys;
642  }
643
644  public void setNetworkKeys(List<UUID> networkKeys) {
645    this.networkKeys = networkKeys;
646  }
647
648  /**
649   * Persisted in the database table.
650   *
651   * @return the frequency with which changes are made to the dataset
652   */
653  @Nullable
654  public MaintenanceUpdateFrequency getMaintenanceUpdateFrequency() {
655    return maintenanceUpdateFrequency;
656  }
657  /**
658   * Persisted in the database table.
659   */
660  public void setMaintenanceUpdateFrequency(MaintenanceUpdateFrequency maintenanceUpdateFrequency) {
661    this.maintenanceUpdateFrequency = maintenanceUpdateFrequency;
662  }
663
664  /**
665   * A description of the maintenance frequency of this resource.
666   *
667   * @return the description of the maintenance frequency of this resource
668   */
669  public String getMaintenanceDescription() {
670    return maintenanceDescription;
671  }
672
673  public void setMaintenanceDescription(String maintenanceDescription) {
674    this.maintenanceDescription = maintenanceDescription;
675  }
676
677  /**
678   * Persisted in the database table.
679   * </br>
680   * Note for backwards compatibility, we cannot apply @NotNull to license. Otherwise existing users of our API
681   * would have to ensure Dataset objects always populate license.
682   * </br>
683   * In the Registry DB, Dataset.license defaults to CC-BY 4.0. Therefore license must be excluded from lenientEquals
684   * method.
685   *
686   * @return the License applied to the dataset
687   *
688   * @see <a href="http://dev.gbif.org/issues/browse/POR-3133">POR-3133</a>
689   */
690  public License getLicense() {
691    return license;
692  }
693
694  /**
695   * Persisted in the database table. Can be interpreted from EML.intellectualRights using machine readable format:
696   * <pre>
697   * {@code
698   * <intellectualRights>
699   *   <para>This work is licensed under a <ulink url="http://creativecommons.org/licenses/by/4.0/legalcode"><citetitle>Creative Commons Attribution (CC-BY) 4.0 License</citetitle></ulink>.</para>
700   * </intellectualRights>
701   * }
702   * </pre>
703   */
704  public void setLicense(License license) {
705    this.license = license;
706  }
707
708  /**
709   * @return the primary DOI for this dataset regardless if issued by GBIF or publisher
710   */
711  public DOI getDoi() {
712    return doi;
713  }
714
715  public void setDoi(DOI doi) {
716    this.doi = doi;
717  }
718
719  public boolean isExternal() {
720    return external;
721  }
722
723  /**
724   * Persisted in the database table.
725   */
726  public void setExternal(boolean external) {
727    this.external = external;
728  }
729
730  @Min(0)
731  public int getNumConstituents() {
732    return numConstituents;
733  }
734
735  /**
736   * Not persisted in the database table, but calculated on the fly.
737   */
738  public void setNumConstituents(int numConstituents) {
739    this.numConstituents = numConstituents;
740  }
741
742  @NotNull
743  public DatasetType getType() {
744    return type;
745  }
746
747  /**
748   * Persisted in the database table.
749   */
750  public void setType(DatasetType type) {
751    this.type = type;
752  }
753
754  @Nullable
755  public DatasetSubtype getSubtype() {
756    return subtype;
757  }
758
759  /**
760   * Persisted in the database table.
761   */
762  public void setSubtype(DatasetSubtype subtype) {
763    this.subtype = subtype;
764  }
765
766  @Nullable
767  public String getShortName() {
768    return shortName;
769  }
770
771  public void setShortName(String shortName) {
772    this.shortName = shortName;
773  }
774
775  /**
776   * TODO: Document what this is
777   */
778  @Nullable
779  @Size(min = 2, max = 50)
780  public String getAlias() {
781    return alias;
782  }
783
784  /**
785   * Persisted in the database table.
786   */
787  public void setAlias(String alias) {
788    this.alias = alias;
789  }
790
791  /**
792   * TODO: Document what this is
793   * TODO: are both alias & abbreviation needed?
794   */
795  @Nullable
796  @Size(min = 1, max = 50)
797  public String getAbbreviation() {
798    return abbreviation;
799  }
800
801  /**
802   * Persisted in the database table.
803   */
804  public void setAbbreviation(String abbreviation) {
805    this.abbreviation = abbreviation;
806  }
807
808  @NotNull
809  public Language getLanguage() {
810    return language;
811  }
812
813  /**
814   * Persisted in the database table.
815   */
816  public void setLanguage(Language language) {
817    this.language = language;
818  }
819
820  @HttpURI
821  @Nullable
822  public URI getHomepage() {
823    return homepage;
824  }
825
826  /**
827   * Persisted in the database table.
828   */
829  public void setHomepage(URI homepage) {
830    this.homepage = homepage;
831  }
832
833  @HttpURI
834  @Nullable
835  public URI getLogoUrl() {
836    return logoUrl;
837  }
838
839  /**
840   * Persisted in the database table.
841   */
842  public void setLogoUrl(URI logoUrl) {
843    this.logoUrl = logoUrl;
844  }
845
846  /**
847   * The exact form of how to cite this dataset.
848   */
849  @Nullable
850  @Valid
851  public Citation getCitation() {
852    return citation;
853  }
854
855  /**
856   * Persisted in the database table.
857   */
858  public void setCitation(Citation citation) {
859    this.citation = citation;
860  }
861
862  /**
863   * A generated list of contacts used in the citation text when it is generated by the GBIF API.
864   */
865  @Nullable
866  public List<CitationContact> getContactsCitation() {
867    return contactsCitation;
868  }
869
870  public void setContactsCitation(List<CitationContact> contactsCitation) {
871    this.contactsCitation = contactsCitation;
872  }
873
874  /**
875   * Any kind of (copy)rights/IPR statements that apply to the datasets data.
876   */
877  @Nullable
878  @Size(min = 1)
879  public String getRights() {
880    return rights;
881  }
882
883  /**
884   * Persisted in the database table.
885   */
886  public void setRights(String rights) {
887    this.rights = rights;
888  }
889
890  public boolean isLockedForAutoUpdate() {
891    return lockedForAutoUpdate;
892  }
893
894  /**
895   * Persisted in the database table.
896   */
897  public void setLockedForAutoUpdate(boolean lockedForAutoUpdate) {
898    this.lockedForAutoUpdate = lockedForAutoUpdate;
899  }
900
901  @Override
902  public String getCreatedBy() {
903    return createdBy;
904  }
905
906  /**
907   * Persisted in the database table.
908   */
909  @Override
910  public void setCreatedBy(String createdBy) {
911    this.createdBy = createdBy;
912  }
913
914  @Override
915  public String getModifiedBy() {
916    return modifiedBy;
917  }
918
919  /**
920   * Persisted in the database table.
921   */
922  @Override
923  public void setModifiedBy(String modifiedBy) {
924    this.modifiedBy = modifiedBy;
925  }
926
927  @Override
928  public List<Contact> getContacts() {
929    return contacts;
930  }
931
932  @Override
933  public void setContacts(List<Contact> contacts) {
934    this.contacts = contacts;
935  }
936
937  @Override
938  public List<Endpoint> getEndpoints() {
939    return endpoints;
940  }
941
942  @Override
943  public void setEndpoints(List<Endpoint> endpoints) {
944    this.endpoints = endpoints;
945  }
946
947  @Override
948  public void addEndpoint(Endpoint endpoint) {
949    endpoints.add(endpoint);
950  }
951
952  @Override
953  public List<MachineTag> getMachineTags() {
954    return machineTags;
955  }
956
957  @Override
958  public void setMachineTags(List<MachineTag> machineTags) {
959    this.machineTags = machineTags;
960  }
961
962  @Override
963  public void addMachineTag(MachineTag machineTag) {
964    machineTags.add(machineTag);
965  }
966
967  @Override
968  public List<Tag> getTags() {
969    return tags;
970  }
971
972  @Override
973  public void setTags(List<Tag> tags) {
974    this.tags = tags;
975  }
976
977  @Override
978  public List<Identifier> getIdentifiers() {
979    return identifiers;
980  }
981
982  @Override
983  public void setIdentifiers(List<Identifier> identifiers) {
984    this.identifiers = identifiers;
985  }
986
987  @Override
988  public List<Comment> getComments() {
989    return comments;
990  }
991
992  @Override
993  public void setComments(List<Comment> comments) {
994    this.comments = comments;
995  }
996
997  public List<Citation> getBibliographicCitations() {
998    return bibliographicCitations;
999  }
1000
1001  public void setBibliographicCitations(List<Citation> bibliographicCitations) {
1002    this.bibliographicCitations = bibliographicCitations;
1003  }
1004
1005  public List<CuratorialUnitComposite> getCuratorialUnits() {
1006    return curatorialUnits;
1007  }
1008
1009  public void setCuratorialUnits(List<CuratorialUnitComposite> curatorialUnits) {
1010    this.curatorialUnits = curatorialUnits;
1011  }
1012
1013  public List<TaxonomicCoverages> getTaxonomicCoverages() {
1014    return taxonomicCoverages;
1015  }
1016
1017  public void setTaxonomicCoverages(List<TaxonomicCoverages> taxonomicCoverages) {
1018    this.taxonomicCoverages = taxonomicCoverages;
1019  }
1020
1021  public String getGeographicCoverageDescription() {
1022    return geographicCoverageDescription;
1023  }
1024
1025  public void setGeographicCoverageDescription(String geographicCoverageDescription) {
1026    this.geographicCoverageDescription = geographicCoverageDescription;
1027  }
1028
1029  public List<GeospatialCoverage> getGeographicCoverages() {
1030    return geographicCoverages;
1031  }
1032
1033  public void setGeographicCoverages(List<GeospatialCoverage> geographicCoverages) {
1034    this.geographicCoverages = geographicCoverages;
1035  }
1036
1037  public List<TemporalCoverage> getTemporalCoverages() {
1038    return temporalCoverages;
1039  }
1040
1041  public void setTemporalCoverages(List<TemporalCoverage> temporalCoverages) {
1042    this.temporalCoverages = temporalCoverages;
1043  }
1044
1045  public List<KeywordCollection> getKeywordCollections() {
1046    return keywordCollections;
1047  }
1048
1049  public void setKeywordCollections(List<KeywordCollection> keywordCollections) {
1050    this.keywordCollections = keywordCollections;
1051  }
1052
1053  public Project getProject() {
1054    return project;
1055  }
1056
1057  public void setProject(Project project) {
1058    this.project = project;
1059  }
1060
1061  public SamplingDescription getSamplingDescription() {
1062    return samplingDescription;
1063  }
1064
1065  public void setSamplingDescription(SamplingDescription samplingDescription) {
1066    this.samplingDescription = samplingDescription;
1067  }
1068
1069  public Set<Country> getCountryCoverage() {
1070    return countryCoverage;
1071  }
1072
1073  public void setCountryCoverage(Set<Country> countryCoverage) {
1074    this.countryCoverage = countryCoverage;
1075  }
1076
1077  public List<Collection> getCollections() {
1078    return collections;
1079  }
1080
1081  public void setCollections(List<Collection> collections) {
1082    this.collections = collections;
1083  }
1084
1085  public List<DataDescription> getDataDescriptions() {
1086    return dataDescriptions;
1087  }
1088
1089  public void setDataDescriptions(List<DataDescription> dataDescriptions) {
1090    this.dataDescriptions = dataDescriptions;
1091  }
1092
1093  public Language getDataLanguage() {
1094    return dataLanguage;
1095  }
1096
1097  public void setDataLanguage(Language dataLanguage) {
1098    this.dataLanguage = dataLanguage;
1099  }
1100
1101  public String getPurpose() {
1102    return purpose;
1103  }
1104
1105  public void setPurpose(String purpose) {
1106    this.purpose = purpose;
1107  }
1108
1109  @Nullable
1110  public String getIntroduction() {
1111    return introduction;
1112  }
1113
1114  public void setIntroduction(String introduction) {
1115    this.introduction = introduction;
1116  }
1117
1118  @Nullable
1119  public String getGettingStarted() {
1120    return gettingStarted;
1121  }
1122
1123  public void setGettingStarted(String gettingStarted) {
1124    this.gettingStarted = gettingStarted;
1125  }
1126
1127  @Nullable
1128  public String getAcknowledgements() {
1129    return acknowledgements;
1130  }
1131
1132  public void setAcknowledgements(String acknowledgements) {
1133    this.acknowledgements = acknowledgements;
1134  }
1135
1136  public String getAdditionalInfo() {
1137    return additionalInfo;
1138  }
1139
1140  public void setAdditionalInfo(String additionalInfo) {
1141    this.additionalInfo = additionalInfo;
1142  }
1143
1144  public Date getPubDate() {
1145    return pubDate;
1146  }
1147
1148  public void setPubDate(Date pubDate) {
1149    this.pubDate = pubDate;
1150  }
1151
1152  @Nullable
1153  @Valid
1154  public DwcA getDwca() {
1155    return dwca;
1156  }
1157
1158  public void setDwca(DwcA dwca) {
1159    this.dwca = dwca;
1160  }
1161
1162  public Set<String> getCategory() {
1163    return category;
1164  }
1165
1166  public void setCategory(Set<String> category) {
1167    this.category = category;
1168  }
1169
1170  @Override
1171  public boolean equals(Object o) {
1172    if (this == o) {
1173      return true;
1174    }
1175    if (o == null || getClass() != o.getClass()) {
1176      return false;
1177    }
1178    Dataset dataset = (Dataset) o;
1179    return external == dataset.external
1180        && numConstituents == dataset.numConstituents
1181        && lockedForAutoUpdate == dataset.lockedForAutoUpdate
1182        && Objects.equals(key, dataset.key)
1183        && Objects.equals(parentDatasetKey, dataset.parentDatasetKey)
1184        && Objects.equals(duplicateOfDatasetKey, dataset.duplicateOfDatasetKey)
1185        && Objects.equals(installationKey, dataset.installationKey)
1186        && Objects.equals(publishingOrganizationKey, dataset.publishingOrganizationKey)
1187        && Objects.equals(publishingOrganizationName, dataset.publishingOrganizationName)
1188        && Objects.equals(networkKeys, dataset.networkKeys)
1189        && Objects.equals(doi, dataset.doi)
1190        && Objects.equals(version, dataset.version)
1191        && type == dataset.type
1192        && subtype == dataset.subtype
1193        && Objects.equals(shortName, dataset.shortName)
1194        && Objects.equals(title, dataset.title)
1195        && Objects.equals(alias, dataset.alias)
1196        && Objects.equals(abbreviation, dataset.abbreviation)
1197        && Objects.equals(description, dataset.description)
1198        && language == dataset.language
1199        && Objects.equals(homepage, dataset.homepage)
1200        && Objects.equals(logoUrl, dataset.logoUrl)
1201        && Objects.equals(citation, dataset.citation)
1202        && Objects.equals(contactsCitation, dataset.contactsCitation)
1203        && Objects.equals(rights, dataset.rights)
1204        && Objects.equals(createdBy, dataset.createdBy)
1205        && Objects.equals(modifiedBy, dataset.modifiedBy)
1206        && Objects.equals(created, dataset.created)
1207        && Objects.equals(modified, dataset.modified)
1208        && Objects.equals(deleted, dataset.deleted)
1209        && Objects.equals(contacts, dataset.contacts)
1210        && Objects.equals(endpoints, dataset.endpoints)
1211        && Objects.equals(machineTags, dataset.machineTags)
1212        && Objects.equals(tags, dataset.tags)
1213        && Objects.equals(identifiers, dataset.identifiers)
1214        && Objects.equals(comments, dataset.comments)
1215        && Objects.equals(bibliographicCitations, dataset.bibliographicCitations)
1216        && Objects.equals(curatorialUnits, dataset.curatorialUnits)
1217        && Objects.equals(taxonomicCoverages, dataset.taxonomicCoverages)
1218        && Objects.equals(geographicCoverageDescription, dataset.geographicCoverageDescription)
1219        && Objects.equals(geographicCoverages, dataset.geographicCoverages)
1220        && Objects.equals(temporalCoverages, dataset.temporalCoverages)
1221        && Objects.equals(keywordCollections, dataset.keywordCollections)
1222        && Objects.equals(project, dataset.project)
1223        && Objects.equals(samplingDescription, dataset.samplingDescription)
1224        && Objects.equals(countryCoverage, dataset.countryCoverage)
1225        && Objects.equals(collections, dataset.collections)
1226        && Objects.equals(dataDescriptions, dataset.dataDescriptions)
1227        && dataLanguage == dataset.dataLanguage
1228        && Objects.equals(purpose, dataset.purpose)
1229        && Objects.equals(introduction, dataset.introduction)
1230        && Objects.equals(gettingStarted, dataset.gettingStarted)
1231        && Objects.equals(acknowledgements, dataset.acknowledgements)
1232        && Objects.equals(additionalInfo, dataset.additionalInfo)
1233        && Objects.equals(pubDate, dataset.pubDate)
1234        && maintenanceUpdateFrequency == dataset.maintenanceUpdateFrequency
1235        && Objects.equals(maintenanceDescription, dataset.maintenanceDescription)
1236        && license == dataset.license
1237        && Objects.equals(dwca, dataset.dwca)
1238        && Objects.equals(category, dataset.category);
1239  }
1240
1241  @Override
1242  public int hashCode() {
1243    return Objects.hash(
1244        key,
1245        parentDatasetKey,
1246        duplicateOfDatasetKey,
1247        installationKey,
1248        publishingOrganizationKey,
1249        publishingOrganizationName,
1250        networkKeys,
1251        doi,
1252        version,
1253        external,
1254        numConstituents,
1255        type,
1256        subtype,
1257        shortName,
1258        title,
1259        alias,
1260        abbreviation,
1261        description,
1262        language,
1263        homepage,
1264        logoUrl,
1265        citation,
1266        contactsCitation,
1267        rights,
1268        lockedForAutoUpdate,
1269        createdBy,
1270        modifiedBy,
1271        created,
1272        modified,
1273        deleted,
1274        contacts,
1275        endpoints,
1276        machineTags,
1277        tags,
1278        identifiers,
1279        comments,
1280        bibliographicCitations,
1281        curatorialUnits,
1282        taxonomicCoverages,
1283        geographicCoverageDescription,
1284        geographicCoverages,
1285        temporalCoverages,
1286        keywordCollections,
1287        project,
1288        samplingDescription,
1289        countryCoverage,
1290        collections,
1291        dataDescriptions,
1292        dataLanguage,
1293        purpose,
1294        introduction,
1295        gettingStarted,
1296        acknowledgements,
1297        additionalInfo,
1298        pubDate,
1299        maintenanceUpdateFrequency,
1300        maintenanceDescription,
1301        license,
1302        dwca,
1303        category);
1304  }
1305
1306  @Override
1307  public String toString() {
1308    return new StringJoiner(", ", Dataset.class.getSimpleName() + "[", "]")
1309        .add("key=" + key)
1310        .add("parentDatasetKey=" + parentDatasetKey)
1311        .add("duplicateOfDatasetKey=" + duplicateOfDatasetKey)
1312        .add("installationKey=" + installationKey)
1313        .add("publishingOrganizationKey=" + publishingOrganizationKey)
1314        .add("publishingOrganizationName=" + publishingOrganizationName)
1315        .add("networkKeys=" + networkKeys)
1316        .add("doi=" + doi)
1317        .add("version='" + version + "'")
1318        .add("external=" + external)
1319        .add("numConstituents=" + numConstituents)
1320        .add("type=" + type)
1321        .add("subtype=" + subtype)
1322        .add("shortName='" + shortName + "'")
1323        .add("title='" + title + "'")
1324        .add("alias='" + alias + "'")
1325        .add("abbreviation='" + abbreviation + "'")
1326        .add("description='" + description + "'")
1327        .add("language=" + language)
1328        .add("homepage=" + homepage)
1329        .add("logoUrl=" + logoUrl)
1330        .add("citation=" + citation)
1331        .add("contactsCitation=" + contactsCitation)
1332        .add("rights='" + rights + "'")
1333        .add("lockedForAutoUpdate=" + lockedForAutoUpdate)
1334        .add("createdBy='" + createdBy + "'")
1335        .add("modifiedBy='" + modifiedBy + "'")
1336        .add("created=" + created)
1337        .add("modified=" + modified)
1338        .add("deleted=" + deleted)
1339        .add("contacts=" + contacts)
1340        .add("endpoints=" + endpoints)
1341        .add("machineTags=" + machineTags)
1342        .add("tags=" + tags)
1343        .add("identifiers=" + identifiers)
1344        .add("comments=" + comments)
1345        .add("bibliographicCitations=" + bibliographicCitations)
1346        .add("curatorialUnits=" + curatorialUnits)
1347        .add("taxonomicCoverages=" + taxonomicCoverages)
1348        .add("geographicCoverageDescription='" + geographicCoverageDescription + "'")
1349        .add("geographicCoverages=" + geographicCoverages)
1350        .add("temporalCoverages=" + temporalCoverages)
1351        .add("keywordCollections=" + keywordCollections)
1352        .add("project=" + project)
1353        .add("samplingDescription=" + samplingDescription)
1354        .add("countryCoverage=" + countryCoverage)
1355        .add("collections=" + collections)
1356        .add("dataDescriptions=" + dataDescriptions)
1357        .add("dataLanguage=" + dataLanguage)
1358        .add("purpose='" + purpose + "'")
1359        .add("introduction='" + introduction + "'")
1360        .add("gettingStarted='" + gettingStarted + "'")
1361        .add("acknowledgements='" + acknowledgements + "'")
1362        .add("additionalInfo='" + additionalInfo + "'")
1363        .add("pubDate=" + pubDate)
1364        .add("maintenanceUpdateFrequency=" + maintenanceUpdateFrequency)
1365        .add("maintenanceDescription='" + maintenanceDescription + "'")
1366        .add("license=" + license)
1367        .add("dwca=" + dwca)
1368        .add("category=" + category)
1369        .add("approximateCounts=" + approximateCounts)
1370        .toString();
1371  }
1372
1373  /**
1374   * Only checks the persisted properties, excluding the server controlled fields (key, created, license etc).
1375   * Does not include the nested properties.
1376   */
1377  @Override
1378  public boolean lenientEquals(Dataset other) {
1379    if (this == other) {
1380      return true;
1381    }
1382    if (other == null) return false;
1383    return Objects.equals(this.parentDatasetKey, other.parentDatasetKey)
1384        && Objects.equals(this.duplicateOfDatasetKey, other.duplicateOfDatasetKey)
1385        && Objects.equals(this.installationKey, other.installationKey)
1386        && Objects.equals(this.publishingOrganizationKey, other.publishingOrganizationKey)
1387        && Objects.equals(this.publishingOrganizationName, other.publishingOrganizationName)
1388        && Objects.equals(this.doi, other.doi)
1389        && Objects.equals(this.external, other.external)
1390        && Objects.equals(this.type, other.type)
1391        && Objects.equals(this.subtype, other.subtype)
1392        && Objects.equals(this.title, other.title)
1393        && Objects.equals(this.alias, other.alias)
1394        && Objects.equals(this.abbreviation, other.abbreviation)
1395        && Objects.equals(this.description, other.description)
1396        && Objects.equals(this.language, other.language)
1397        && Objects.equals(this.homepage, other.homepage)
1398        && Objects.equals(this.logoUrl, other.logoUrl)
1399        && Objects.equals(this.citation, other.citation)
1400        && Objects.equals(this.rights, other.rights)
1401        && Objects.equals(this.lockedForAutoUpdate, other.lockedForAutoUpdate)
1402        && Objects.equals(this.deleted, other.deleted)
1403        && Objects.equals(this.maintenanceUpdateFrequency, other.maintenanceUpdateFrequency)
1404        && Objects.equals(this.maintenanceDescription, other.maintenanceDescription)
1405        && Objects.equals(this.dwca, other.dwca)
1406        && Objects.equals(this.category, other.category);
1407  }
1408
1409  /**
1410   * Metadata of dataset that has been published as a Darwin Core Archive (DwC-A).
1411   */
1412  @NoArgsConstructor
1413  @Data
1414  public static class DwcA {
1415    @Schema(
1416      description = "This attribute, within the <core>, indicates the specific " +
1417                    "type of data being represented in the core data file.**."
1418    )
1419    private String coreType;
1420
1421    @Schema(
1422      description = "This attribute, within the <extensions>, indicates the specific " +
1423                    "type of data being represented in the associated extension data file.**."
1424    )
1425    private List<String> extensions;
1426
1427    @Schema(
1428      description = "Timestamp of when the dataset DwcA metadata was modified.",
1429      accessMode = Schema.AccessMode.READ_ONLY
1430    )
1431    @Null(groups = {PrePersist.class})
1432    private Date modified;
1433
1434
1435    public String getCoreType() {
1436      return coreType;
1437    }
1438
1439    public void setCoreType(String coreType) {
1440      this.coreType = coreType;
1441    }
1442
1443    @Nullable
1444    public List<String> getExtensions() {
1445      return extensions;
1446    }
1447
1448    public void setExtensions(List<String> extensions) {
1449      this.extensions = extensions;
1450    }
1451
1452    @Nullable
1453    public Date getModified() {
1454      return modified;
1455    }
1456
1457    public void setModified(Date modified) {
1458      this.modified = modified;
1459    }
1460  }
1461
1462  /**
1463   * Metadata of dataset that has been published as a <a href="https://specs.frictionlessdata.io/data-package/">DataPackage</a>.
1464   */
1465  @NoArgsConstructor
1466  @Data
1467  @Experimental
1468  public static class DataPackage {
1469
1470    /**
1471     * Custom deserializer to capture raw JSON string.
1472     */
1473    public static class RawJsonDeserializer extends JsonDeserializer<String> {
1474
1475      public RawJsonDeserializer() {
1476        // Jackson requires this
1477      }
1478
1479      @Override
1480      public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
1481        JsonNode node = p.readValueAsTree();
1482        return node.toString(); // store the raw JSON as a string
1483      }
1484    }
1485
1486    @Schema(
1487      description = "The  content of the <a href=\"https://specs.frictionlessdata.io/schemas/data-package.json\">datapackage.json</a> file."
1488    )
1489    @JsonDeserialize(using = RawJsonDeserializer.class)
1490    @JsonRawValue
1491    private String metadata;
1492
1493    @Schema(
1494      description = "Timestamp of when the dataset datapackage metadata was modified.",
1495      accessMode = Schema.AccessMode.READ_ONLY
1496    )
1497    @Null(groups = {PrePersist.class})
1498    private Date modified;
1499
1500
1501    @Schema(
1502      description = "Unique GBIF key for the dataset.",
1503      accessMode = Schema.AccessMode.READ_ONLY
1504    )
1505    private UUID datasetKey;
1506
1507    @Schema(
1508      description = "Unique GBIF key for the dataset endpoint.",
1509      accessMode = Schema.AccessMode.READ_ONLY
1510    )
1511    private Integer endpointKey;
1512
1513    @NotNull
1514    public String getMetadata() {
1515      return metadata;
1516    }
1517
1518    public void setMetadata(String metadata) {
1519      this.metadata = metadata;
1520    }
1521
1522    @Nullable
1523    public Date getModified() {
1524      return modified;
1525    }
1526
1527    public void setModified(Date modified) {
1528      this.modified = modified;
1529    }
1530
1531    @NotNull
1532    public UUID getDatasetKey() {
1533      return datasetKey;
1534    }
1535
1536    public void setDatasetKey(UUID datasetKey) {
1537      this.datasetKey = datasetKey;
1538    }
1539
1540    @NotNull
1541    public Integer getEndpointKey() {
1542      return endpointKey;
1543    }
1544
1545    public void setEndpointKey(Integer endpointKey) {
1546      this.endpointKey = endpointKey;
1547    }
1548  }
1549}