diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 2cae4aae9f6..aea585e084f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -80,6 +80,7 @@ import edu.harvard.iq.dataverse.datasetutility.TwoRavensHelper; import edu.harvard.iq.dataverse.datasetutility.WorldMapPermissionHelper; import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; +import edu.harvard.iq.dataverse.engine.command.impl.GetLatestPublishedDatasetVersionCommand; import edu.harvard.iq.dataverse.engine.command.impl.RequestRsyncScriptCommand; import edu.harvard.iq.dataverse.engine.command.impl.PublishDatasetResult; import edu.harvard.iq.dataverse.engine.command.impl.RestrictFileCommand; @@ -3944,23 +3945,6 @@ public String getDescription() { return workingVersion.getDescriptionPlainText(); } - /** - * dataset publication date unpublished datasets will return an empty - * string. - * - * @return String dataset publication date (dd MMM yyyy). - */ - public String getPublicationDate() { - assert (null != workingVersion); - if (DatasetVersion.VersionState.DRAFT == workingVersion.getVersionState()) { - return ""; - } - Date rel_date = workingVersion.getReleaseTime(); - SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); - String r = fmt.format(rel_date.getTime()); - return r; - } - /** * dataset authors * @@ -3971,16 +3955,6 @@ public List getDatasetAuthors() { return workingVersion.getDatasetAuthorNames(); } - /** - * dataset subjects - * - * @return array of String containing the subjects for a page - */ - public List getDatasetSubjects() { - assert (null != workingVersion); - return workingVersion.getDatasetSubjects(); - } - /** * publisher (aka - name of root dataverse) * @@ -4066,4 +4040,36 @@ public List getDatasetSummaryFields() { return DatasetUtil.getDatasetSummaryFields(workingVersion, customFields); } + Boolean thisLatestReleasedVersion = null; + + public boolean isThisLatestReleasedVersion() { + if (thisLatestReleasedVersion != null) { + return thisLatestReleasedVersion; + } + + if (!workingVersion.isPublished()) { + thisLatestReleasedVersion = false; + return false; + } + + DatasetVersion latestPublishedVersion = null; + Command cmd = new GetLatestPublishedDatasetVersionCommand(dvRequestService.getDataverseRequest(), dataset); + try { + latestPublishedVersion = commandEngine.submit(cmd); + } catch (Exception ex) { + // whatever... + } + + thisLatestReleasedVersion = workingVersion.equals(latestPublishedVersion); + + return thisLatestReleasedVersion; + + } + + public String getJsonLd() { + if (isThisLatestReleasedVersion()) { + return workingVersion.getJsonLd(); + } + return ""; + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java index 030a10244a2..4865079430c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java @@ -3,8 +3,10 @@ import edu.harvard.iq.dataverse.util.MarkupChecker; import edu.harvard.iq.dataverse.DatasetFieldType.FieldType; import edu.harvard.iq.dataverse.util.StringUtil; +import edu.harvard.iq.dataverse.util.SystemConfig; import edu.harvard.iq.dataverse.workflows.WorkflowComment; import java.io.Serializable; +import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -17,6 +19,9 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -142,6 +147,9 @@ public enum License { @Transient private String contributorNames; + + @Transient + private String jsonLd; @OneToMany(mappedBy="datasetVersion", cascade={CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) private List datasetVersionUsers; @@ -417,6 +425,10 @@ public boolean isReleased() { return versionState.equals(VersionState.RELEASED); } + public boolean isPublished() { + return isReleased(); + } + public boolean isDraft() { return versionState.equals(VersionState.DRAFT); } @@ -706,6 +718,42 @@ public List getDatasetAuthors() { return retList; } + public List getTimePeriodsCovered() { + List retList = new ArrayList<>(); + for (DatasetField dsf : this.getDatasetFields()) { + if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.timePeriodCovered)) { + for (DatasetFieldCompoundValue timePeriodValue : dsf.getDatasetFieldCompoundValues()) { + String start = ""; + String end = ""; + for (DatasetField subField : timePeriodValue.getChildDatasetFields()) { + if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.timePeriodCoveredStart)) { + if (subField.isEmptyForDisplay()) { + start = null; + } else { + // we want to use "getValue()", as opposed to "getDisplayValue()" here - + // as the latter method prepends the value with the word "Start:"! + start = subField.getValue(); + } + } + if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.timePeriodCoveredEnd)) { + if (subField.isEmptyForDisplay()) { + end = null; + } else { + // see the comment above + end = subField.getValue(); + } + } + + } + if (start != null && end != null) { + retList.add(start + "/" + end); + } + } + } + } + return retList; + } + /** * @return List of Strings containing the names of the authors. */ @@ -729,7 +777,55 @@ public List getDatasetSubjects() { } return subjects; } - + + /** + * @return List of Strings containing the version's Topic Classifications + */ + public List getTopicClassifications() { + return getCompoundChildFieldValues(DatasetFieldConstant.topicClassification, DatasetFieldConstant.topicClassValue); + } + + /** + * @return List of Strings containing the version's Keywords + */ + public List getKeywords() { + return getCompoundChildFieldValues(DatasetFieldConstant.keyword, DatasetFieldConstant.keywordValue); + } + + /** + * @return List of Strings containing the version's PublicationCitations + */ + public List getPublicationCitationValues() { + return getCompoundChildFieldValues(DatasetFieldConstant.publication, DatasetFieldConstant.publicationCitation); + } + + /** + * @param parentFieldName compound dataset field A (from DatasetFieldConstant.*) + * @param childFieldName dataset field B, child field of A (from DatasetFieldConstant.*) + * @return List of values of the child field + */ + public List getCompoundChildFieldValues(String parentFieldName, String childFieldName) { + List keywords = new ArrayList<>(); + for (DatasetField dsf : this.getDatasetFields()) { + if (dsf.getDatasetFieldType().getName().equals(parentFieldName)) { + for (DatasetFieldCompoundValue keywordFieldValue : dsf.getDatasetFieldCompoundValues()) { + for (DatasetField subField : keywordFieldValue.getChildDatasetFields()) { + if (subField.getDatasetFieldType().getName().equals(childFieldName)) { + String keyword = subField.getValue(); + // Field values should NOT be empty or, especially, null, + // - in the ideal world. But as we are realizing, they CAN + // be null in real life databases. So, a check, just in case: + if (!StringUtil.isEmpty(keyword)) { + keywords.add(subField.getValue()); + } + } + } + } + } + } + return keywords; + } + public String getDatasetProducersString(){ String retVal = ""; for (DatasetField dsf : this.getDatasetFields()) { @@ -1099,4 +1195,170 @@ public List getWorkflowComments() { return workflowComments; } + /** + * dataset publication date unpublished datasets will return an empty + * string. + * + * @return String dataset publication date in ISO 8601 format (yyyy-MM-dd). + */ + public String getPublicationDateAsString() { + if (DatasetVersion.VersionState.DRAFT == this.getVersionState()) { + return ""; + } + Date rel_date = this.getReleaseTime(); + SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); + String r = fmt.format(rel_date.getTime()); + return r; + } + + // TODO: Make this more performant by writing the output to the database or a file? + // Agree - now that this has grown into a somewhat complex chunk of formatted + // metadata - and not just a couple of values inserted into the page html - + // it feels like it would make more sense to treat it as another supported + // export format, that can be produced once and cached. + // The problem with that is that the export subsystem assumes there is only + // one metadata export in a given format per dataset (it uses the current + // released (published) version. This JSON fragment is generated for a + // specific released version - and we can have multiple released versions. + // So something will need to be modified to accommodate this. -- L.A. + + public String getJsonLd() { + // We show published datasets only for "datePublished" field below. + if (!this.isPublished()) { + return ""; + } + + if (jsonLd != null) { + return jsonLd; + } + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("@context", "http://schema.org"); + job.add("@type", "Dataset"); + job.add("identifier", this.getDataset().getPersistentURL()); + job.add("name", this.getTitle()); + JsonArrayBuilder authors = Json.createArrayBuilder(); + for (DatasetAuthor datasetAuthor : this.getDatasetAuthors()) { + JsonObjectBuilder author = Json.createObjectBuilder(); + String name = datasetAuthor.getName().getValue(); + String affiliation = datasetAuthor.getAffiliation().getValue(); + // We are aware of "givenName" and "familyName" but instead of a person it might be an organization such as "Gallup Organization". + //author.add("@type", "Person"); + author.add("name", name); + if (!StringUtil.isEmpty(affiliation)) { + author.add("affiliation", affiliation); + } + authors.add(author); + } + job.add("author", authors); + /** + * We are aware that there is a "datePublished" field but it means "Date + * of first broadcast/publication." This only makes sense for a 1.0 + * version. + */ + String datePublished = this.getDataset().getPublicationDateFormattedYYYYMMDD(); + if (datePublished != null) { + job.add("datePublished", datePublished); + } + + /** + * "dateModified" is more appropriate for a version: "The date on which + * the CreativeWork was most recently modified or when the item's entry + * was modified within a DataFeed." + */ + job.add("dateModified", this.getPublicationDateAsString()); + job.add("version", this.getVersionNumber().toString()); + job.add("description", this.getDescriptionPlainText()); + /** + * "keywords" - contains subject(s), datasetkeyword(s) and topicclassification(s) + * metadata fields for the version. -- L.A. + * (see #2243 for details/discussion/feedback from Google) + */ + JsonArrayBuilder keywords = Json.createArrayBuilder(); + + for (String subject : this.getDatasetSubjects()) { + keywords.add(subject); + } + + for (String topic : this.getTopicClassifications()) { + keywords.add(topic); + } + + for (String keyword : this.getKeywords()) { + keywords.add(keyword); + } + + job.add("keywords", keywords); + + /** + * citation: + * (multiple) publicationCitation values, if present: + */ + + List publicationCitations = getPublicationCitationValues(); + if (publicationCitations.size() > 0) { + JsonArrayBuilder citation = Json.createArrayBuilder(); + for (String pubCitation : publicationCitations) { + //citationEntry.add("@type", "Dataset"); + //citationEntry.add("text", pubCitation); + citation.add(pubCitation); + } + job.add("citation", citation); + } + + /** + * temporalCoverage: + * (if available) + */ + + List timePeriodsCovered = this.getTimePeriodsCovered(); + if (timePeriodsCovered.size() > 0) { + JsonArrayBuilder temporalCoverage = Json.createArrayBuilder(); + for (String timePeriod : timePeriodsCovered) { + temporalCoverage.add(timePeriod); + } + job.add("temporalCoverage", temporalCoverage); + } + + /** + * spatialCoverage (if available) + * TODO + * (punted, for now - see #2243) + * + */ + + /** + * funder (if available) + * TODO + * (punted, for now - see #2243) + */ + + job.add("schemaVersion", "https://schema.org/version/3.3"); + + TermsOfUseAndAccess terms = this.getTermsOfUseAndAccess(); + if (terms != null) { + JsonObjectBuilder license = Json.createObjectBuilder().add("@type", "Dataset"); + + if (TermsOfUseAndAccess.License.CC0.equals(terms.getLicense())) { + license.add("text", "CC0").add("url", "https://creativecommons.org/publicdomain/zero/1.0/"); + } else { + license.add("text", terms.getTermsOfUse()); + } + + job.add("license",license); + } + + job.add("includedInDataCatalog", Json.createObjectBuilder() + .add("@type", "DataCatalog") + .add("name", this.getRootDataverseNameforCitation()) + .add("url", SystemConfig.getDataverseSiteUrlStatic()) + ); + + job.add("provider", Json.createObjectBuilder() + .add("@type", "Organization") + .add("name", "Dataverse") + ); + jsonLd = job.build().toString(); + return jsonLd; + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java index c99d99a4b05..b69631eac12 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java @@ -277,6 +277,10 @@ public static int getMinutesUntilPasswordResetTokenExpires() { * by the Settings Service configuration. */ public String getDataverseSiteUrl() { + return getDataverseSiteUrlStatic(); + } + + public static String getDataverseSiteUrlStatic() { String hostUrl = System.getProperty(SITE_URL); if (hostUrl != null && !"".equals(hostUrl)) { return hostUrl; diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml index b425fbc8de1..08f3987b51d 100755 --- a/src/main/webapp/dataset.xhtml +++ b/src/main/webapp/dataset.xhtml @@ -24,16 +24,21 @@ - + - + + + + diff --git a/src/main/webapp/dataverse_template.xhtml b/src/main/webapp/dataverse_template.xhtml index 1d581b1f19d..d082b3cb77d 100644 --- a/src/main/webapp/dataverse_template.xhtml +++ b/src/main/webapp/dataverse_template.xhtml @@ -13,7 +13,9 @@ <h:outputText value="#{pageTitle}"/> + + diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionTest.java index c0c40c52597..44f75db9833 100644 --- a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionTest.java @@ -1,9 +1,18 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.mocks.MocksFactory; +import java.io.StringReader; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; @@ -85,4 +94,47 @@ public void testIsInReview() { assertFalse(nonDraft.isInReview()); } + @Test + public void testGetJsonLd() throws ParseException { + Dataset dataset = new Dataset(); + dataset.setProtocol("doi"); + dataset.setAuthority("10.5072/FK2"); + dataset.setIdentifier("LK0D1H"); + DatasetVersion datasetVersion = new DatasetVersion(); + datasetVersion.setDataset(dataset); + datasetVersion.setVersionState(DatasetVersion.VersionState.DRAFT); + assertEquals("", datasetVersion.getPublicationDateAsString()); + // Only published datasets return any JSON. + assertEquals("", datasetVersion.getJsonLd()); + datasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + datasetVersion.setVersionNumber(1L); + SimpleDateFormat dateFmt = new SimpleDateFormat("yyyyMMdd"); + Date publicationDate = dateFmt.parse("19551105"); + datasetVersion.setReleaseTime(publicationDate); + dataset.setPublicationDate(new Timestamp(publicationDate.getTime())); + Dataverse dataverse = new Dataverse(); + dataverse.setName("LibraScholar"); + dataset.setOwner(dataverse); + String jsonLd = datasetVersion.getJsonLd(); + System.out.println("jsonLd: " + jsonLd); + JsonReader jsonReader = Json.createReader(new StringReader(jsonLd)); + JsonObject obj = jsonReader.readObject(); + assertEquals("http://schema.org", obj.getString("@context")); + assertEquals("Dataset", obj.getString("@type")); + assertEquals("http://dx.doi.org/10.5072/FK2/LK0D1H", obj.getString("identifier")); + assertEquals("https://schema.org/version/3.3", obj.getString("schemaVersion")); + assertEquals("1955-11-05", obj.getString("dateModified")); + assertEquals("1955-11-05", obj.getString("datePublished")); + assertEquals("1", obj.getString("version")); + // TODO: if it ever becomes easier to mock a dataset title, test it. + assertEquals("", obj.getString("name")); + // TODO: If it ever becomes easier to mock authors, test them. + JsonArray emptyArray = Json.createArrayBuilder().build(); + assertEquals(emptyArray, obj.getJsonArray("author")); + // TODO: If it ever becomes easier to mock subjects, test them. + assertEquals(emptyArray, obj.getJsonArray("keywords")); + assertEquals("Dataverse", obj.getJsonObject("provider").getString("name")); + assertEquals("LibraScholar", obj.getJsonObject("includedInDataCatalog").getString("name")); + } + }