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