Java tutorial
package org.gbif.ipt.model; import org.gbif.api.model.common.DOI; import org.gbif.dwc.terms.Term; import org.gbif.dwc.terms.TermFactory; import org.gbif.ipt.config.Constants; import org.gbif.ipt.model.voc.IdentifierStatus; import org.gbif.ipt.model.voc.PublicationMode; import org.gbif.ipt.model.voc.PublicationStatus; import org.gbif.ipt.service.AlreadyExistingException; import org.gbif.ipt.utils.ResourceUtils; import org.gbif.metadata.eml.Agent; import org.gbif.metadata.eml.Citation; import org.gbif.metadata.eml.Eml; import org.gbif.metadata.eml.MaintenanceUpdateFrequency; import java.io.Serializable; import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import static com.google.common.base.Objects.equal; /** * The main class to represent an IPT resource. * Its enumerated type property defines the kind of resource (Metadata, Checklist, Occurrence) * A resource can be identified by its short name which has to be unique within an IPT instance. */ public class Resource implements Serializable, Comparable<Resource> { public enum CoreRowType { OCCURRENCE, CHECKLIST, SAMPLINGEVENT, METADATA, OTHER } private static Logger log = Logger.getLogger(Resource.class); private static final TermFactory TERM_FACTORY = TermFactory.instance(); private static final long serialVersionUID = 3832626162173352190L; private String shortname; // unique private Eml eml = new Eml(); private String coreType; private String subtype; // update frequency private MaintenanceUpdateFrequency updateFrequency; // publication status private PublicationStatus status = PublicationStatus.PRIVATE; // publication mode private PublicationMode publicationMode; // is resource citation to be auto-generated? private boolean citationAutoGenerated; // resource version and eml version are the same private BigDecimal emlVersion; // resource version replaced private BigDecimal replacedEmlVersion; // last time resource was successfully published private Date lastPublished; // next time resource is scheduled to be pubished private Date nextPublished; // core record count private int recordsPublished = 0; // record counts by extension: Map<rowType, count> private Map<String, Integer> recordsByExtension = Maps.newHashMap(); // registry data - only exists when status=REGISTERED private UUID key; private Organisation organisation; // resource meta-metadata private User creator; private Date created; private User modifier; private Date modified; private Date metadataModified; private Date mappingsModified; private Date sourcesModified; private Set<User> managers = new HashSet<User>(); // mapping configs private Set<Source> sources = new HashSet<Source>(); private List<ExtensionMapping> mappings = Lists.newArrayList(); private String changeSummary; private List<VersionHistory> versionHistory = Lists.newLinkedList(); private IdentifierStatus identifierStatus = IdentifierStatus.UNRESERVED; private DOI doi; private UUID doiOrganisationKey; public void addManager(User manager) { if (manager != null) { this.managers.add(manager); } } /** * Add new VersionHistory, as long as a VersionHistory with same version hasn't been added yet. The new version * gets added at the top of the list. * * @param history VersionHistory to add */ public void addVersionHistory(VersionHistory history) { Preconditions.checkNotNull(history); boolean exists = false; for (VersionHistory vh : getVersionHistory()) { if (vh.getVersion().equals(history.getVersion())) { exists = true; } } if (!exists) { log.debug("Adding new version history: " + history.getVersion()); versionHistory.add(0, history); } } /** * Remove a VersionHistory with specific version. * * @param version version of VersionHistory to remove */ public void removeVersionHistory(BigDecimal version) { if (version != null) { Iterator<VersionHistory> iter = getVersionHistory().iterator(); while (iter.hasNext()) { BigDecimal historyVersion = new BigDecimal(iter.next().getVersion()); if (version.compareTo(historyVersion) == 0) { iter.remove(); } } } } /** * Find and return a VersionHistory with specific version. * * @param version version of VersionHistory searched for * * @return VersionHistory with specific version or null if not found */ public VersionHistory findVersionHistory(BigDecimal version) { if (version != null) { for (VersionHistory vh : getVersionHistory()) { if (version.toPlainString().equals(new BigDecimal(vh.getVersion()).toPlainString())) { return vh; } } } return null; } /** * Adds a new extension mapping to the resource. For non core extensions a core extension must exist already. * It returns the list index for this mapping according to getMappings(rowType) * * @return list index corresponding to getMappings(rowType) or null if the mapping couldnt be added * * @throws IllegalArgumentException if no core mapping exists when adding a non core mapping */ public Integer addMapping(@Nullable ExtensionMapping mapping) throws IllegalArgumentException { if (mapping != null && mapping.getExtension() != null) { if (!mapping.isCore() && !hasCore()) { throw new IllegalArgumentException("Cannot add extension mapping before a core mapping exists"); } Integer index = getMappings(mapping.getExtension().getRowType()).size(); this.mappings.add(mapping); return index; } return null; } public void addSource(Source src, boolean allowOverwrite) throws AlreadyExistingException { // make sure we talk about the same resource src.setResource(this); if (!allowOverwrite && sources.contains(src)) { throw new AlreadyExistingException(); } if (allowOverwrite && sources.contains(src)) { // If source file is going to be overwritten, it should be actually re-add it. sources.remove(src); // Changing the SourceBase in the ExtensionMapping object from the mapping list. for (ExtensionMapping ext : this.getMappings()) { if (ext.getSource().equals(src)) { ext.setSource(src); } } } sources.add(src); } /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Resource o) { return shortname.compareToIgnoreCase(o.shortname); } /** * Delete a Resource's mapping. If the mapping gets successfully deleted, and the mapping is a core type mapping, * and there are no additional core type mappings, all other mappings are also cleared. * * @param mapping ExtensionMapping * * @return if deletion was successful or not */ public boolean deleteMapping(ExtensionMapping mapping) { if (mapping != null && mappings.contains(mapping)) { // what's the core row type? String coreRowType = getCoreRowType(); // is this the resource's core mapping? if (coreRowType != null && coreRowType.equalsIgnoreCase(mapping.getExtension().getRowType())) { // are there multiple core mappings? List<ExtensionMapping> coreMappings = getMappings(coreRowType); if (coreMappings.size() > 1) { // if it's the first mapping in the list, swap it with next mapping to retain coreType if (mappings.indexOf(mapping) == 0) { ExtensionMapping next = coreMappings.get(1); int nextIndex = mappings.indexOf(next); log.debug("Swapping first core mapping with next core mapping with index#" + String.valueOf(nextIndex)); Collections.swap(mappings, 0, nextIndex); } log.debug("Deleting core mapping..."); return mappings.remove(mapping); } // if this was the only core mapping, delete all mappings else { log.debug("Deleting only core mapping and thus clearing all mappings..."); mappings.clear(); return true; } } else { log.debug("Deleting non-core mapping..."); return mappings.remove(mapping); } } log.debug("Mapping was null, or resource no longer has this mapping, thus it could not be deleted!"); return false; } public boolean deleteSource(Source src) { boolean result = false; if (src != null) { result = sources.remove(src); // also remove existing mappings List<ExtensionMapping> ems = new ArrayList<ExtensionMapping>(mappings); for (ExtensionMapping em : ems) { if (em.getSource() != null && src.equals(em.getSource())) { deleteMapping(em); log.debug("Cascading source delete to mapping " + em.getExtension().getTitle()); } } } return result; } /** * @return true if Source is mapped, false otherwise */ public boolean hasMappedSource(Source src) { if (src != null) { for (ExtensionMapping em : new ArrayList<ExtensionMapping>(mappings)) { if (em.getSource() != null && src.equals(em.getSource())) { log.debug("Source mapped to " + em.getExtension().getTitle()); return true; } } } return false; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof Resource)) { return false; } Resource o = (Resource) other; return equal(shortname, o.shortname); } /** * @return all core mappings, excluding extension mappings with core row types */ public List<ExtensionMapping> getCoreMappings() { List<ExtensionMapping> cores = new ArrayList<ExtensionMapping>(); String coreRowType = getCoreRowType(); for (ExtensionMapping m : mappings) { if (m.isCore() && coreRowType != null && coreRowType.equalsIgnoreCase(m.getExtension().getRowType())) { cores.add(m); } } return cores; } /** * @return the row type of the first core extension mapping, which always determines the core row type */ @Nullable public String getCoreRowType() { for (ExtensionMapping m : mappings) { if (m.isCore()) { return m.getExtension().getRowType(); } } return null; } /** * At first the core type can be set during resource creation or on the basic metadata page. But once * a core mapping has been done, it is derived from the core mapping. * * @return the core type. */ @Nullable public String getCoreType() { String coreRowType = getCoreRowType(); if (coreRowType != null) { if (coreRowType.equalsIgnoreCase(Constants.DWC_ROWTYPE_TAXON)) { coreType = StringUtils.capitalize(CoreRowType.CHECKLIST.toString()); } else if (coreRowType.equalsIgnoreCase(Constants.DWC_ROWTYPE_OCCURRENCE)) { coreType = StringUtils.capitalize(CoreRowType.OCCURRENCE.toString()); } else if (coreRowType.equalsIgnoreCase(Constants.DWC_ROWTYPE_EVENT)) { coreType = StringUtils.capitalize(CoreRowType.SAMPLINGEVENT.toString()); } else { coreType = StringUtils.capitalize(CoreRowType.OTHER.toString()); } } return coreType; } public Term getCoreTypeTerm() { List<ExtensionMapping> cores = getCoreMappings(); if (!cores.isEmpty()) { return TERM_FACTORY.findTerm(cores.get(0).getExtension().getRowType()); } return null; } public Date getCreated() { return created; } @NotNull public User getCreator() { return creator; } public Eml getEml() { return eml; } /** * Get resource version. Same as EML version. * * @return resource version */ @NotNull public BigDecimal getEmlVersion() { return (emlVersion == null) ? eml.getEmlVersion() : emlVersion; } /** * Get the next resource version. If the resource has never been published, the next resource version * is 1.0. If no new DOI has been reserved for the resource, the version is bumped by a minor resource version. * If a new DOI has been reserved for the resource, and the resource's visibility is public, the version is bumped by * a major resource version. * * @return next resource version */ @NotNull public BigDecimal getNextVersion() { // first publication retrieve existing version if (lastPublished == null) { return getEml().getEmlVersion(); } // There are two cases that warrant a new major version, provided a doi has been reserved for resource // #1: no DOI has been assigned yet, and resource's visibility is public (or registered) // #2: a DOI has been assigned already if (doi != null && identifierStatus == IdentifierStatus.PUBLIC_PENDING_PUBLICATION) { if (!isAlreadyAssignedDoi() && (status == PublicationStatus.PUBLIC || status == PublicationStatus.REGISTERED)) { return getEml().getNextEmlVersionAfterMajorVersionChange(); } else if (isAlreadyAssignedDoi()) { return getEml().getNextEmlVersionAfterMajorVersionChange(); } } // all other cases warrant a minor version increment return getEml().getNextEmlVersionAfterMinorVersionChange(); } /** * @return true if the resource has already been assigned a DOI, false otherwise. Remember only DOIs that are public * have officially been assigned/registered. */ public boolean isAlreadyAssignedDoi() { VersionHistory last = getLastPublishedVersion(); if (last != null) { DOI doi = last.getDoi(); IdentifierStatus status = last.getStatus(); if (doi != null && status == IdentifierStatus.PUBLIC) { return true; } } return false; } /** * @return the DOI assigned/registered to the last published public version. */ @Nullable public DOI getAssignedDoi() { VersionHistory last = getLastPublishedVersion(); if (isAlreadyAssignedDoi() && last != null) { return last.getDoi(); } return null; } /** * @return version number of last published version, or null if last published version doesn't exist */ @Nullable public BigDecimal getLastPublishedVersionsVersion() { VersionHistory last = getLastPublishedVersion(); if (last != null) { return new BigDecimal(last.getVersion()); } return null; } /** * @return change summary of last published resource, or null if last published version had none */ @Nullable public String getLastPublishedVersionsChangeSummary() { VersionHistory last = getLastPublishedVersion(); if (last != null) { return Strings.emptyToNull(last.getChangeSummary()); } return null; } /** * @return publication status of last published version, defaulting to status=private if it is not definitively known */ @NotNull public PublicationStatus getLastPublishedVersionsPublicationStatus() { VersionHistory last = getLastPublishedVersion(); if (last != null) { return last.getPublicationStatus(); } else if (status.equals(PublicationStatus.REGISTERED)) { return PublicationStatus.REGISTERED; } else { return PublicationStatus.PRIVATE; } } /** * @return VersionHistory of last published version, or null if last published version doesn't exist */ @Nullable public VersionHistory getLastPublishedVersion() { // first version history with released date represents last published version for (VersionHistory vh : getVersionHistory()) { if (vh.getReleased() != null) { return vh; } } return null; } /** * @return version number of last version (VersionHistory), or null if last version doesn't exist */ @Nullable public BigDecimal getLastVersionHistoryVersion() { if (!getVersionHistory().isEmpty()) { return new BigDecimal(getVersionHistory().get(0).getVersion()); } return null; } /** * @return GBIF UUID, key assigned when resource is registered with GBIF */ public UUID getKey() { return key; } /** * Return the date the resource was last published successfully. * * @return the date the resource was last published successfully */ public Date getLastPublished() { return lastPublished; } /** * Return the date the resource is scheduled to be published next. * * @return the date the resource is scheduled to be published next. */ public Date getNextPublished() { return nextPublished; } public Set<User> getManagers() { return managers; } /** * @return a list of extensions that have been mapped to, starting with the extension that was mapped first (core * mapping), and ending with the extension that was mapped last. Elements in the list are unique. */ public List<Extension> getMappedExtensions() { LinkedHashSet<Extension> extensions = Sets.newLinkedHashSet(); for (ExtensionMapping em : mappings) { if (em.getExtension() != null && em.getSource() != null) { extensions.add(em.getExtension()); } else { log.error("ExtensionMapping referencing NULL Extension or Source for resource: " + getShortname()); } } return Lists.newArrayList(extensions); } public ExtensionMapping getMapping(String rowType, Integer index) { if (rowType != null && index != null) { List<ExtensionMapping> maps = getMappings(rowType); if (maps.size() >= index) { return maps.get(index); } } return null; } public List<ExtensionMapping> getMappings() { return mappings; } /** * Get the list of mappings for the requested extension rowtype. * The order of mappings in the list is guaranteed to be stable and the same as the underlying original mappings * list. * * @param rowType identifying the extension * * @return the list of mappings for the requested extension rowtype */ public List<ExtensionMapping> getMappings(String rowType) { List<ExtensionMapping> maps = new ArrayList<ExtensionMapping>(); if (rowType != null) { for (ExtensionMapping m : mappings) { if (rowType.equals(m.getExtension().getRowType())) { maps.add(m); } } } return maps; } public Date getModified() { return modified; } public User getModifier() { return modifier; } public Organisation getOrganisation() { return organisation; } public int getRecordsPublished() { return recordsPublished; } public String getShortname() { return shortname; } public Source getSource(String name) { if (name == null) { return null; } name = SourceBase.normaliseName(name); for (Source s : sources) { if (s.getName().equals(name)) { return s; } } return null; } public List<Source> getSources() { return Ordering.natural().nullsLast().onResultOf(new Function<Source, String>() { @Nullable public String apply(@Nullable Source src) { return (src == null) ? null : src.getName(); } }).sortedCopy(sources); } @NotNull public PublicationStatus getStatus() { return status; } /** * @return the status of the DOI */ @NotNull public IdentifierStatus getIdentifierStatus() { return identifierStatus; } /** * @param identifierStatus the status of the DOI (should be paired with resource.doi) */ public void setIdentifierStatus(@NotNull IdentifierStatus identifierStatus) { this.identifierStatus = identifierStatus; } /** * @return the DOI of the resource, always in prefix/suffix format excluding "doi:", e.g. 10.1234/qu83ng */ @Nullable public DOI getDoi() { return doi; } /** * @param doi the DOI of the resource (should be paired with resource.identifierStatus) */ public void setDoi(@Nullable DOI doi) { this.doi = doi; } /** * @return the key of the organisation that assigned the DOI to the resource */ @Nullable public UUID getDoiOrganisationKey() { return doiOrganisationKey; } /** * @param doiOrganisationKey the key of the organisation that assigned the DOI to the resource */ public void setDoiOrganisationKey(@Nullable UUID doiOrganisationKey) { this.doiOrganisationKey = doiOrganisationKey; } /** * Return the PublicationMode of the resource. Default is PublicationMode.AUTO_PUBLISH_OFF meaning that the * resource must be republished manually, and that the resource has not been configured yet for auto-publishing. * * @return the PublicationMode of the resource, or PublicationMode.AUTO_PUBLISH_OFF if not set yet */ public PublicationMode getPublicationMode() { return (publicationMode == null) ? PublicationMode.AUTO_PUBLISH_OFF : publicationMode; } public String getSubtype() { return subtype; } /** * Return the frequency with which changes and additions are made to the dataset after the initial dataset is * completed. * * @return the maintenance update frequency */ @Nullable public MaintenanceUpdateFrequency getUpdateFrequency() { return updateFrequency; } public String getTitle() { if (eml != null) { return eml.getTitle(); } return null; } /** * Build and return string composed of resource title and shortname in brackets if the title and shortname are * different. This string can be called to construct log messages. * * @return constructed string */ public String getTitleAndShortname() { StringBuilder sb = new StringBuilder(); if (eml != null) { sb.append(eml.getTitle()); if (!shortname.equalsIgnoreCase(eml.getTitle())) { sb.append(" (").append(shortname).append(")"); } } return sb.toString(); } /** * @return true if this resource is mapped to at least one core extension */ public boolean hasCore() { return getCoreTypeTerm() != null; } @Override public int hashCode() { return Objects.hashCode(shortname); } public boolean hasMappedData() { for (ExtensionMapping cm : getCoreMappings()) { // test each core mapping if there is at least one field mapped if (!cm.getFields().isEmpty()) { return true; } } return false; } public boolean hasPublishedData() { return recordsPublished > 0; } public boolean isPublished() { return lastPublished != null; } /** * @return true if the last published version is public, false otherwise */ public boolean isLastPublishedVersionPublic() { VersionHistory last = getLastPublishedVersion(); if (last != null) { return last.getPublicationStatus().equals(PublicationStatus.PUBLIC); } return false; } /** * Used before publishing a new version. * * @return true if the resource has been assigned a GBIF-supported license, false otherwise */ public boolean isAssignedGBIFSupportedLicense() { return eml.parseLicenseUrl() != null && Constants.GBIF_SUPPORTED_LICENSES.contains(eml.parseLicenseUrl()); } public boolean isRegistered() { return key != null && status.equals(PublicationStatus.REGISTERED); } public void setCoreType(@Nullable String coreType) { this.coreType = Strings.isNullOrEmpty(coreType) ? null : coreType; } public void setCreated(Date created) { this.created = created; if (modified == null) { modified = created; } } public void setCreator(User creator) { this.creator = creator; if (modifier == null) { modifier = creator; } } public void setEml(Eml eml) { this.eml = eml; } /** * Set the new eml (resource) version. If the new version is greater than the existing version, the previous version * is stored. * </br> * Please note that comparison on the minor version number must include trailing zeros, e.g. 1.10 > 1.9 * * @param v new eml (resource) version */ public void setEmlVersion(BigDecimal v) { if (ResourceUtils.assertVersionOrder(v, emlVersion)) { setReplacedEmlVersion(new BigDecimal(emlVersion.toPlainString())); } emlVersion = v; eml.setEmlVersion(v); } public void setKey(UUID key) { this.key = key; } public void setLastPublished(Date lastPublished) { this.lastPublished = lastPublished; } public void setNextPublished(Date nextPublished) { this.nextPublished = nextPublished; } public void setManagers(Set<User> managers) { this.managers = managers; } public void setMappings(List<ExtensionMapping> extensions) { this.mappings = extensions; } public void setModified(Date modified) { this.modified = modified; } public void setModifier(User modifier) { this.modifier = modifier; } public void setOrganisation(Organisation organisation) { this.organisation = organisation; } public void setRecordsPublished(int recordsPublished) { this.recordsPublished = recordsPublished; } public void setShortname(String shortname) { this.shortname = shortname; if (eml != null && eml.getTitle() == null) { eml.setTitle(shortname); } } public void setStatus(PublicationStatus status) { this.status = status; } /** * Sets the resource PublicationMode. Its value must come from the Enumeration PublicationMode. * * @param publicationMode PublicationMode */ public void setPublicationMode(PublicationMode publicationMode) { this.publicationMode = publicationMode; } /** * Sets the resource subtype. If it is null or an empty string, it is set to null. Otherwise, it is simply set * in lowercase. * * @param subtype subtype String */ public void setSubtype(String subtype) { this.subtype = (Strings.isNullOrEmpty(subtype)) ? null : subtype.toLowerCase(); } /** * Sets the maintenance update frequency. Its value comes in as a String, and gets matched to the Enumeration * MainUpFreqType. If no match occurs, the value is set to null. * * @param updateFrequency MainUpFreqType Enum */ public void setUpdateFrequency(String updateFrequency) { this.updateFrequency = MaintenanceUpdateFrequency.findByIdentifier(updateFrequency); } public void setTitle(String title) { if (eml != null) { this.eml.setTitle(title); } } @Override public String toString() { return "Resource " + shortname; } /** * Check if the resource has been configured for auto-publishing. To qualify, the resource must have an update * frequency suitable for auto-publishing (annually, biannually, monthly, weekly, daily) or have a next published * date that isn't null, and must have auto-publishing mode turned on. * * @return true if the resource uses auto-publishing */ public boolean usesAutoPublishing() { return publicationMode == PublicationMode.AUTO_PUBLISH_ON && updateFrequency != null; } /** * @return the change summary, explaining what has changed in this version compared with the last */ public String getChangeSummary() { return changeSummary; } /** * @param changeSummary the change summary, explaining what has changed in this version compared with the last */ public void setChangeSummary(String changeSummary) { this.changeSummary = changeSummary; } /** * @return the version history */ @NotNull public List<VersionHistory> getVersionHistory() { if (versionHistory == null) { versionHistory = Lists.newLinkedList(); } return versionHistory; } public void setVersionHistory(List<VersionHistory> versionHistory) { this.versionHistory = versionHistory; } /** * @return the version about to be replaced by the next publication (if publication is in progress), * or the version that has been replaced by the latest publication (if publication finished). */ @NotNull public BigDecimal getReplacedEmlVersion() { return (replacedEmlVersion == null) ? Constants.INITIAL_RESOURCE_VERSION : replacedEmlVersion; } /** * Set the replacedEmlVersion, only if that version exists in VersionHistory. * * @param replacedEmlVersion version to be replaced, or that has been replaced */ public void setReplacedEmlVersion(BigDecimal replacedEmlVersion) { VersionHistory vh = findVersionHistory(replacedEmlVersion); if (vh == null) { log.error("Replaced version (" + replacedEmlVersion.toPlainString() + ") does not exist in version history!"); } else { this.replacedEmlVersion = replacedEmlVersion; } } /** * @return true if resource is publicly available, or false otherwise (e.g. it is private or deleted) */ public boolean isPubliclyAvailable() { return status.equals(PublicationStatus.PUBLIC) || status.equals(PublicationStatus.REGISTERED); } /** * @return true if the resource citation (EML) should be auto-generated during publication, false otherwise */ public boolean isCitationAutoGenerated() { return citationAutoGenerated; } /** * @param citationAutoGenerated true if the citation should be auto-generated during publication, false otherwise */ public void setCitationAutoGenerated(boolean citationAutoGenerated) { this.citationAutoGenerated = citationAutoGenerated; } /** * @return map containing record counts (map value) by extension (map key) */ public Map<String, Integer> getRecordsByExtension() { return recordsByExtension; } /** * @param recordsByExtension map of record counts (map value) by extension (map key) */ public void setRecordsByExtension(Map<String, Integer> recordsByExtension) { this.recordsByExtension = recordsByExtension; } /** * Construct the resource citation from various parts for the version specified. * </br> * This method is called from the Citation metadata page, in order to preview the resource citation for the upcoming * version for example. * </br> * The citation format is: * Creators (PublicationYear): Title. Version. Publisher. ResourceType. Identifier * * @param version resource version to use in citation * @param homepage homepage URI * * @return generated resource citation string, or null if it failed to be generated */ public String generateResourceCitation(@NotNull String version, @NotNull String homepage) { try { return generateResourceCitation(new BigDecimal(version), new URI(homepage)); } catch (URISyntaxException e) { log.error("Failed to generate URI for homepage string: " + homepage, e); } return null; } /** * Construct the resource citation from various parts for the version specified. * </br> * The citation format is: * Creators (PublicationYear): Title. Version. Publisher. ResourceType. Identifier * * @param version resource version to use in citation * @param homepage homepage URI * * @return generated resource citation string */ public String generateResourceCitation(@NotNull BigDecimal version, @NotNull URI homepage) { StringBuilder sb = new StringBuilder(); // make list of verified authors (having first and last name) List<String> verifiedAuthorList = Lists.newArrayList(); for (Agent creator : getEml().getCreators()) { String authorName = getAuthorName(creator); if (authorName != null) { verifiedAuthorList.add(authorName); } } // add comma separated authors Iterator<String> iter = verifiedAuthorList.iterator(); while (iter.hasNext()) { sb.append(iter.next()); if (iter.hasNext()) { sb.append(", "); } } // add year resource was first published (captured in EML dateStamp) int publicationYear = getPublicationYear(getEml().getDateStamp()); if (publicationYear > 0) { sb.append(" ("); sb.append(publicationYear); sb.append("): "); } // add title sb.append((StringUtils.trimToNull(getTitle()) == null) ? getShortname() : StringUtils.trim(getTitle())); sb.append(". "); // add version sb.append("v"); sb.append(version.toPlainString()); sb.append(". "); // add publisher String publisher = (getOrganisation() == null) ? null : StringUtils.trimToNull(getOrganisation().getName()); if (publisher != null) { sb.append(publisher); sb.append(". "); } // add ResourceTypeGeneral/ResourceType, e.g. Dataset/Occurrence, Dataset/Checklist sb.append("Dataset"); if (getCoreType() != null) { sb.append("/"); sb.append(StringUtils.capitalize(getCoreType().toLowerCase())); } sb.append(". "); // add DOI as the identifier. DataCite recommends using linkable, permanent URL if (getDoi() != null) { sb.append(getDoi().getUrl()); } // otherwise add the citation identifier instead else if (getEml().getCitation() != null && !Strings.isNullOrEmpty(getEml().getCitation().getIdentifier())) { sb.append(getEml().getCitation().getIdentifier()); } // otherwise use its IPT homepage as the identifier else { sb.append(homepage.toString()); } return sb.toString(); } /** * Construct author name for citation. Name must have a last name and at least one first name to be included. If * both the first and last name are left blank on purpose, the organisation name can be used as an alternative. * * @param creator creator * * @return author name */ @VisibleForTesting protected String getAuthorName(Agent creator) { StringBuilder sb = new StringBuilder(); String lastName = StringUtils.trimToNull(creator.getLastName()); String firstNames = StringUtils.trimToNull(creator.getFirstName()); String organisation = StringUtils.trimToNull(creator.getOrganisation()); if (lastName != null && firstNames != null) { sb.append(lastName); sb.append(" "); // add first initial of each first name, capitalized String[] names = firstNames.split("\\s+"); for (int i = 0; i < names.length; i++) { sb.append(StringUtils.upperCase(String.valueOf(names[i].charAt(0)))); if (i < names.length - 1) { sb.append(" "); } } } else if (lastName == null && firstNames == null && organisation != null) { sb.append(organisation); } return sb.toString(); } /** * Get the year from the publication date. * * @param publicationDate date resource was published * * @return publication year */ @VisibleForTesting protected int getPublicationYear(Date publicationDate) { Calendar calendar = Calendar.getInstance(); calendar.setTime(publicationDate); return calendar.get(Calendar.YEAR); } /** * @return the date the metadata was last modified. */ public Date getMetadataModified() { return metadataModified; } /** * Set metadataModified date. Update modified date at same time. * * @param metadataModified date metadata was last modified */ public void setMetadataModified(Date metadataModified) { this.modified = metadataModified; this.metadataModified = metadataModified; } /** * @return the date any source mapping was last modified. */ public Date getMappingsModified() { return mappingsModified; } /** * Set mappingsModified date. Update modified date at same time. * * @param mappingsModified date mappings were last modified */ public void setMappingsModified(Date mappingsModified) { this.modified = mappingsModified; this.mappingsModified = mappingsModified; } /** * @return the date any source was last modified. */ public Date getSourcesModified() { return sourcesModified; } /** * Set sourcesModified date. Update modified date at same time. * * @param sourcesModified date sources were last modified */ public void setSourcesModified(Date sourcesModified) { this.modified = sourcesModified; this.sourcesModified = sourcesModified; } /** * Updates the resource's list of alternate identifiers for the resource's DOI, adding it or removing it depending on * the status of the DOI: * </br> * If the status of the DOI is reserved or public, the resource DOI will be added as the first alternative identifier * in the list. Please note that multiple (different) DOIs are allowed in the list of alternate identifiers. * </br> * If the status of the DOI is unavailable, the resource DOI will be removed from the list. */ public synchronized void updateAlternateIdentifierForDOI() { Preconditions.checkNotNull(eml); if (doi != null) { // retrieve a list of the resource's alternate identifiers List<String> ids = eml.getAlternateIdentifiers(); if (identifierStatus.equals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION) || identifierStatus.equals(IdentifierStatus.PUBLIC)) { // make sure the DOI always appears first List<String> reorderedList = Lists.newArrayList(); reorderedList.add(doi.toString()); // make sure the DOI doesn't appear twice for (String id : ids) { if (!id.equalsIgnoreCase(doi.toString())) { reorderedList.add(id); } } // replace the original list with the reordered one if (!ids.isEmpty()) { ids.clear(); } ids.addAll(reorderedList); log.debug("DOI=" + doi.toString() + " added to resource's list of alt ids as first element"); } else if (identifierStatus.equals(IdentifierStatus.UNAVAILABLE) || identifierStatus.equals(IdentifierStatus.UNRESERVED)) { for (Iterator<String> iterator = ids.iterator(); iterator.hasNext();) { String id = iterator.next(); // make sure a DOI that has been made unavailable, or that has been deleted, no longer appears in the list if (id.equalsIgnoreCase(doi.toString())) { iterator.remove(); log.debug("DOI=" + doi.toString() + " removed from resource's list of alt ids"); } } } } } /** * Updates the resource's citation identifier for the resource's DOI, adding it or removing it depending on * the status of the DOI: * </br> * If the status of the DOI is reserved or public, the resource DOI will be set as the resource citation identifier. * </br> * If the status of the DOI is unavailable or unreserved, the resource DOI will be unset as the citation identifier. */ public synchronized void updateCitationIdentifierForDOI() { Preconditions.checkNotNull(eml); if (doi != null) { // retrieve resource's citation identifier Citation citation = eml.getCitation(); if (identifierStatus.equals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION) || identifierStatus.equals(IdentifierStatus.PUBLIC)) { // make sure the DOI set as resource citation identifier if (citation == null) { // resource must have citation if it has a DOI setCitationAutoGenerated(true); eml.setCitation( new Citation("Will be replaced by auto-generated citation", doi.getUrl().toString())); } else { citation.setIdentifier(doi.getUrl().toString()); } log.debug("DOI=" + doi.getUrl().toString() + " set as resource's citation identifier"); } else if (identifierStatus.equals(IdentifierStatus.UNAVAILABLE) || identifierStatus.equals(IdentifierStatus.UNRESERVED)) { // make sure the DOI no longer set as resource citation identifier if (citation == null) { // resource must have had a citation if it had a DOI setCitationAutoGenerated(true); Citation generated = new Citation(); generated.setCitation("Will be replaced by auto-generated citation"); eml.setCitation(generated); } else { citation.setIdentifier(null); } log.debug("DOI=" + doi.getUrl().toString() + " unset as resource's citation identifier"); } } } /** * Determine if this resource has at least one mapping to the occurrence core extension, no matter if the mapping * is a core or extension mapping. * * @return true if resource has at least one mapping to the occurrence core extension, false otherwise */ public boolean hasOccurrenceMapping() { return !getMappings(Constants.DWC_ROWTYPE_OCCURRENCE).isEmpty(); } }