Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b4ca799
Stash: ListDataverseTemplatesCommand and endpoint WIP
GPortas Jun 9, 2025
16fce32
Added: ListDataverseTemplatesCommand.execute logic
GPortas Jun 9, 2025
11b0fce
Merge branch 'develop' of github.com:IQSS/dataverse into 11562-api-ge…
GPortas Jun 9, 2025
3b6bf34
Added: dataverse template API payload params
GPortas Jun 19, 2025
b030b99
Merge branch 'develop' of github.com:IQSS/dataverse into 11562-api-ge…
GPortas Jun 19, 2025
bf1cf66
Added: termsOfUseAndAccess to template payload
GPortas Jun 19, 2025
1cb7813
Added: ListDataverseTemplatesCommandTest
GPortas Jun 20, 2025
ad0910d
Stash: createTemplate API WIP
GPortas Jun 20, 2025
891bd6c
Stash: create template endpoint WIP (CreateInitializedTemplateCommand…
GPortas Jun 23, 2025
855efb1
Refactor: using CreateTemplateCommand instead of separate command for…
GPortas Jun 29, 2025
59d1d2b
Refactor: use final variable
GPortas Jun 29, 2025
058c5be
Added: tweaks to CreateTemplateCommand to support working initialization
GPortas Jun 29, 2025
5b18882
Added: IT for creating and getting templates
GPortas Jun 30, 2025
864ceee
Merge branch 'develop' of github.com:IQSS/dataverse into 11562-api-ge…
GPortas Jun 30, 2025
3a2bc40
Added: CreateTemplateCommandTest
GPortas Jun 30, 2025
1efcc34
Fixed: IT testCreateAndGetTemplates params
GPortas Jun 30, 2025
12c9ba3
Added: docs for listing templates
GPortas Jun 30, 2025
9229795
Merge branch 'develop' of github.com:IQSS/dataverse into 11562-api-ge…
GPortas Jun 30, 2025
ca526ad
Added: docs for dataverse template creation
GPortas Jun 30, 2025
80f7afb
Added: dataverse template json payload file example
GPortas Jun 30, 2025
970e954
Added: release notes for #11562
GPortas Jun 30, 2025
522fd2f
Merge branch 'develop' into 11562-api-get-templates
stevenwinship Jul 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/release-notes/11562-templates-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
New endpoints have been implemented in the Dataverses API for the management of dataverse templates:

- POST `/dataverses/{id}/templates`: Creates a template for a given Dataverse collection ``id``.
- GET `/dataverses/{id}/templates`: Lists the templates for a given Dataverse collection ``id``.
27 changes: 27 additions & 0 deletions doc/sphinx-guides/source/_static/api/dataverse-template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "Dataverse template",
"isDefault": true,
"fields": [
{
"typeName": "author",
"value": [
{
"authorName": {
"typeName": "authorName",
"value": "Belicheck, Bill"
},
"authorAffiliation": {
"typeName": "authorIdentifierScheme",
"value": "ORCID"
}
}
]
}
],
"instructions": [
{
"instructionField": "author",
"instructionText": "The author data"
}
]
}
40 changes: 40 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,46 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X GET "https://demo.dataverse.org/api/access/dataverseFeaturedItemImage/1"

List Templates of a Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Lists the templates for a given Dataverse collection ``id``:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=1

curl -H "X-Dataverse-key:$API_TOKEN" -X GET "$SERVER_URL/api/dataverses/{ID}/templates"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X GET "https://demo.dataverse.org/api/dataverses/1/templates"

Create a Template for a Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Creates a template for a given Dataverse collection ``id``.

To create the template, you must send a JSON file. Your JSON file might look like :download:`dataverse-template.json <../_static/api/dataverse-template.json>` which you would send to the Dataverse installation like this:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=1

curl -H "X-Dataverse-key: $API_TOKEN" -X POST "$SERVER_URL/api/dataverses/{ID}/templates" --upload-file dataverse-template.json

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/dataverses/1/templates" --upload-file dataverse-template.json

Datasets
--------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItemServiceBean;
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
import edu.harvard.iq.dataverse.util.cache.CacheFactoryBean;
import edu.harvard.iq.dataverse.engine.DataverseEngine;
import edu.harvard.iq.dataverse.authorization.Permission;
Expand Down Expand Up @@ -192,6 +193,9 @@ public class EjbDataverseEngine {
@EJB
DataverseFeaturedItemServiceBean dataverseFeaturedItemServiceBean;

@EJB
LicenseServiceBean licenseServiceBean;

@EJB
DatasetFieldsValidator datasetFieldsValidator;

Expand Down Expand Up @@ -541,6 +545,11 @@ public DatasetFieldsValidator datasetFieldsValidator() {
return datasetFieldsValidator;
}

@Override
public LicenseServiceBean licenses() {
return licenseServiceBean;
}

@Override
public StorageUseServiceBean storageUse() {
return storageUseService;
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ public List<DatasetField> getDatasetFields() {

@Transient
private Map<String, String> instructionsMap = null;


public void setInstructionsMap(Map<String, String> instructionsMap) {
this.instructionsMap = instructionsMap;
}

@Transient
private TreeMap<MetadataBlock, List<DatasetField>> metadataBlocksForView = new TreeMap<>();
@Transient
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -1931,4 +1931,34 @@ public Response deleteFeaturedItems(@Context ContainerRequestContext crc, @PathP
return e.getResponse();
}
}

@GET
@AuthRequired
@Path("{identifier}/templates")
public Response getTemplates(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
try {
Dataverse dataverse = findDataverseOrDie(dvIdtf);
return ok(jsonTemplates(execCommand(new ListDataverseTemplatesCommand(createDataverseRequest(getRequestUser(crc)), dataverse))));
} catch (WrappedResponse e) {
return e.getResponse();
}
}

@POST
@AuthRequired
@Path("{identifier}/templates")
public Response createTemplate(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String dvIdtf) {
try {
Dataverse dataverse = findDataverseOrDie(dvIdtf);
NewTemplateDTO newTemplateDTO;
try {
newTemplateDTO = NewTemplateDTO.fromRequestBody(body, jsonParser());
} catch (JsonParseException ex) {
return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.createTemplate.error.jsonParseMetadataFields"), ex.getMessage()));
}
return ok(jsonTemplate(execCommand(new CreateTemplateCommand(newTemplateDTO.toTemplate(), createDataverseRequest(getRequestUser(crc)), dataverse, true))));
} catch (WrappedResponse e) {
return e.getResponse();
}
}
}
75 changes: 75 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/dto/NewTemplateDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package edu.harvard.iq.dataverse.api.dto;

import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.util.json.JsonParseException;
import edu.harvard.iq.dataverse.util.json.JsonParser;
import edu.harvard.iq.dataverse.util.json.JsonUtil;
import jakarta.json.*;

import java.sql.Timestamp;
import java.util.*;

public class NewTemplateDTO {

private String name;
private List<DatasetField> datasetFields;
private Map<String, String> instructionsMap;
private boolean isDefault;

public static NewTemplateDTO fromRequestBody(String requestBody, JsonParser jsonParser) throws JsonParseException {
NewTemplateDTO newTemplateDTO = new NewTemplateDTO();

JsonObject jsonObject = JsonUtil.getJsonObject(requestBody);

newTemplateDTO.name = jsonObject.getString("name");
newTemplateDTO.datasetFields = jsonParser.parseMultipleFields(jsonObject);
newTemplateDTO.instructionsMap = parseRequestBodyInstructionsMap(jsonObject);
newTemplateDTO.isDefault = jsonObject.getBoolean("isDefault", false);

return newTemplateDTO;
}

public Template toTemplate() {
Template template = new Template();

template.setDatasetFields(getDatasetFields());
template.setName(getName());
template.setInstructionsMap(getInstructionsMap());
template.updateInstructions();
template.setCreateTime(new Timestamp(new Date().getTime()));
template.setUsageCount(0L);

return template;
}

public String getName() {
return name;
}

public List<DatasetField> getDatasetFields() {
return datasetFields;
}

public Map<String, String> getInstructionsMap() {
return instructionsMap;
}

public boolean isDefault() {
return isDefault;
}

private static Map<String, String> parseRequestBodyInstructionsMap(JsonObject jsonObject) {
Map<String, String> instructionsMap = new HashMap<>();
JsonArray instructionsJsonArray = jsonObject.getJsonArray("instructions");
if (instructionsJsonArray == null) {
return null;
}
for (JsonObject instructionJsonObject : instructionsJsonArray.getValuesAs(JsonObject.class)) {
instructionsMap.put(
instructionJsonObject.getString("instructionField"),
instructionJsonObject.getString("instructionText")
);
}
return instructionsMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.harvard.iq.dataverse.dataset.DatasetFieldsValidator;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItemServiceBean;
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
import edu.harvard.iq.dataverse.search.IndexServiceBean;
import edu.harvard.iq.dataverse.search.SearchService;
import edu.harvard.iq.dataverse.search.SearchServiceFactory;
Expand Down Expand Up @@ -140,4 +141,6 @@ public interface CommandContext {
public DataverseFeaturedItemServiceBean dataverseFeaturedItems();

public DatasetFieldsValidator datasetFieldsValidator();

public LicenseServiceBean licenses();
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,88 @@

package edu.harvard.iq.dataverse.engine.command.impl;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.Template;

import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.authorization.Permission;

import edu.harvard.iq.dataverse.engine.command.AbstractCommand;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.util.DatasetFieldUtil;

import java.util.ArrayList;
import java.util.List;

/**
*
* @author skraffmiller
* Creates a template {@link Template} for a {@link Dataverse}.
*/
@RequiredPermissions( Permission.EditDataverse )
@RequiredPermissions(Permission.EditDataverse)
public class CreateTemplateCommand extends AbstractCommand<Template> {
private final Template created;
private final Dataverse dv;

public CreateTemplateCommand(Template template, DataverseRequest aRequest, Dataverse anAffectedDataverse) {
super(aRequest, anAffectedDataverse);
created = template;
dv = anAffectedDataverse;
}

@Override
public Template execute(CommandContext ctxt) throws CommandException {

return ctxt.templates().save(created);
}

private final Template template;
private final Dataverse dataverse;

private final boolean initialize;

public CreateTemplateCommand(Template template, DataverseRequest request, Dataverse dataverse) {
this(template, request, dataverse, false);
}

public CreateTemplateCommand(Template template, DataverseRequest request, Dataverse dataverse, boolean initialize) {
super(request, dataverse);
this.template = template;
this.dataverse = dataverse;
this.initialize = initialize;
}

@Override
public Template execute(CommandContext ctxt) throws CommandException {
if (initialize) {
template.setDataverse(dataverse);
template.setMetadataValueBlocks(getSystemMetadataBlocks(ctxt));

updateTermsOfUseAndAccess(ctxt, template);
updateDatasetFieldInputLevels(template, ctxt);

DatasetFieldUtil.tidyUpFields(template.getDatasetFields(), false);
}

return ctxt.templates().save(template);
}

private static void updateTermsOfUseAndAccess(CommandContext ctxt, Template template) {
TermsOfUseAndAccess terms = new TermsOfUseAndAccess();
terms.setFileAccessRequest(true);
terms.setTemplate(template);
terms.setLicense(ctxt.licenses().getDefault());
template.setTermsOfUseAndAccess(terms);
}

private void updateDatasetFieldInputLevels(Template template, CommandContext ctxt) {
Long dvIdForInputLevel = this.dataverse.getId();
if (!this.dataverse.isMetadataBlockRoot()) {
dvIdForInputLevel = this.dataverse.getMetadataRootId();
}

for (DatasetField dsf : template.getFlatDatasetFields()) {
DataverseFieldTypeInputLevel inputLevel = ctxt.fieldTypeInputLevels().findByDataverseIdDatasetFieldTypeId(
dvIdForInputLevel,
dsf.getDatasetFieldType().getId()
);
if (inputLevel != null) {
dsf.setInclude(inputLevel.isInclude());
} else {
dsf.setInclude(true);
}
}
}

private static List<MetadataBlock> getSystemMetadataBlocks(CommandContext ctxt) {
List<MetadataBlock> systemMetadataBlocks = new ArrayList<>();
for (MetadataBlock mdb : ctxt.metadataBlocks().listMetadataBlocks()) {
JvmSettings.MDB_SYSTEM_KEY_FOR.lookupOptional(mdb.getName()).ifPresent(smdbString -> systemMetadataBlocks.add(mdb));
}
return systemMetadataBlocks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.Template;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.engine.command.AbstractCommand;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;

import java.util.*;

/**
* Lists the templates {@link Template} of a {@link Dataverse}.
*/
@RequiredPermissions(Permission.EditDataverse)
public class ListDataverseTemplatesCommand extends AbstractCommand<List<Template>> {

private final Dataverse dataverse;

public ListDataverseTemplatesCommand(DataverseRequest request, Dataverse dataverse) {
super(request, dataverse);
this.dataverse = dataverse;
}

@Override
public List<Template> execute(CommandContext ctxt) throws CommandException {
List<Template> templates = new ArrayList<>();

if (dataverse.getOwner() != null) {
templates.addAll(dataverse.getParentTemplates());
}

templates.addAll(dataverse.getTemplates());

return templates;
}
}
Loading