Skip to content

Commit 74fed3d

Browse files
authored
Merge pull request #11385 from IQSS/10520-datasetTypeLicenses
10520 dataset type licenses
2 parents 8599993 + 459f3f9 commit 74fed3d

File tree

11 files changed

+537
-8
lines changed

11 files changed

+537
-8
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Dataset Types can set available Licenses
2+
3+
Licenses (e.g. "MIT") can now be linked to dataset types (e.g. "software") using new superuser APIs. The create Dataset Type APIs have been extended to allow you to set metadata blocks and/or licenses on the creation of a Dataset Type.
4+
5+
If a license is not available for a given dataset type then the Create Dataset API will prevent that license from being applied to the dataset.
6+
Also, the UI will only show those licenses that are available to a the dataset's dataset type.
7+
8+
For more information, see the guides ([overview](https://dataverse-guide--11385.org.readthedocs.build/en/11385/user/dataset-management.html#dataset-types), [new APIs](https://dataverse-guide--11385.org.readthedocs.build/en/11385/api/native-api.html#set-available-licenses-for-a-dataset-type)), #10519 and #11001.

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3645,21 +3645,21 @@ Add Dataset Type
36453645
36463646
Note: Before you add any types of your own, there should be a single type called "dataset". If you add "software" or "workflow", these types will be sent to DataCite (if you use DataCite). Otherwise, the only functionality you gain currently from adding types is an entry in the "Dataset Type" facet but be advised that if you add a type other than "software" or "workflow", you will need to add your new type to your Bundle.properties file for it to appear in Title Case rather than lower case in the "Dataset Type" facet.
36473647
3648-
With all that said, we'll add a "software" type in the example below. This API endpoint is superuser only. The "name" of a type cannot be only digits.
3648+
With all that said, we'll add a "software" type in the example below. This API endpoint is superuser only. The "name" of a type cannot be only digits. Note that this endpoint also allows you to add metadata blocks and available licenses for your new dataset type by adding "linkedMetadataBlocks" and/or "availableLicenses" arrays to your JSON.
36493649
36503650
.. code-block:: bash
36513651
36523652
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
36533653
export SERVER_URL=https://demo.dataverse.org
3654-
export JSON='{"name": "software"}'
3654+
export JSON='{"name":"software","linkedMetadataBlocks":["codeMeta20"],"availableLicenses":["MIT", "Apache-2.0"]}'
36553655
36563656
curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes" -X POST -d $JSON
36573657
36583658
The fully expanded example above (without environment variables) looks like this:
36593659
36603660
.. code-block:: bash
36613661
3662-
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes" -X POST -d '{"name": "software"}'
3662+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes" -X POST -d '{"name":"software","linkedMetadataBlocks":["codeMeta20"],"availableLicenses":["MIT", "Apache-2.0"]}'
36633663
36643664
.. _api-delete-dataset-type:
36653665
@@ -3712,6 +3712,34 @@ To update the blocks that are linked, send an array with those blocks.
37123712
37133713
To remove all links to blocks, send an empty array.
37143714
3715+
Set Available Licenses for a Dataset Type
3716+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3717+
3718+
With this API a superuser may limit the available licenses for a dataset of a given type to a subset of those available in the installation.
3719+
3720+
For example, a superuser could create a type called "software" and limit the available licenses to only "MIT" and "Apache-2.0".
3721+
3722+
This API endpoint is for superusers only.
3723+
3724+
.. code-block:: bash
3725+
3726+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3727+
export SERVER_URL=https://demo.dataverse.org
3728+
export TYPE=software
3729+
export JSON='["MIT", "Apache-2.0"]'
3730+
3731+
curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $JSON
3732+
3733+
The fully expanded example above (without environment variables) looks like this:
3734+
3735+
.. code-block:: bash
3736+
3737+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["MIT", "Apache-2.0"]'
3738+
3739+
To update the licenses available, send an array with those licenses.
3740+
3741+
To remove all links to licenses, send an empty array. If no licenses are set then the default is all active licenses in the installation.
3742+
37153743
.. _api-dataset-version-note:
37163744
37173745
Dataset Version Notes
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"datasetType": "testDatasetType",
3+
"datasetVersion": {
4+
"license": {
5+
"name": "CC BY 4.0",
6+
"uri": "https://creativecommons.org/licenses/by/4.0/"
7+
},
8+
"metadataBlocks": {
9+
"citation": {
10+
"fields": [
11+
{
12+
"value": "pyDataverse",
13+
"typeClass": "primitive",
14+
"multiple": false,
15+
"typeName": "title"
16+
},
17+
{
18+
"value": [
19+
{
20+
"authorName": {
21+
"value": "Range, Dan",
22+
"typeClass": "primitive",
23+
"multiple": false,
24+
"typeName": "authorName"
25+
},
26+
"authorAffiliation": {
27+
"value": "University of Stuttgart",
28+
"typeClass": "primitive",
29+
"multiple": false,
30+
"typeName": "authorAffiliation"
31+
}
32+
}
33+
],
34+
"typeClass": "compound",
35+
"multiple": true,
36+
"typeName": "author"
37+
},
38+
{
39+
"value": [
40+
{ "datasetContactEmail" : {
41+
"typeClass": "primitive",
42+
"multiple": false,
43+
"typeName": "datasetContactEmail",
44+
"value" : "[email protected]"
45+
},
46+
"datasetContactName" : {
47+
"typeClass": "primitive",
48+
"multiple": false,
49+
"typeName": "datasetContactName",
50+
"value": "Range, Jan"
51+
}
52+
}],
53+
"typeClass": "compound",
54+
"multiple": true,
55+
"typeName": "datasetContact"
56+
},
57+
{
58+
"value": [ {
59+
"dsDescriptionValue":{
60+
"value": "A Python module for Dataverse.",
61+
"multiple":false,
62+
"typeClass": "primitive",
63+
"typeName": "dsDescriptionValue"
64+
}}],
65+
"typeClass": "compound",
66+
"multiple": true,
67+
"typeName": "dsDescription"
68+
},
69+
{
70+
"value": [
71+
"Computer and Information Science"
72+
],
73+
"typeClass": "controlledVocabulary",
74+
"multiple": true,
75+
"typeName": "subject"
76+
}
77+
],
78+
"displayName": "Citation Metadata"
79+
}
80+
}
81+
}
82+
}

src/main/java/edu/harvard/iq/dataverse/DatasetPage.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
import edu.harvard.iq.dataverse.globus.GlobusServiceBean;
134134
import edu.harvard.iq.dataverse.export.SchemaDotOrgExporter;
135135
import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler;
136+
import edu.harvard.iq.dataverse.license.License;
136137
import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean;
137138
import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry;
138139
import java.util.Collections;
@@ -5992,6 +5993,13 @@ public String getCroissant() {
59925993
}
59935994
return null;
59945995
}
5996+
5997+
public List<License> getAvailableLicenses(){
5998+
if(!workingVersion.getDataset().getDatasetType().getLicenses().isEmpty()){
5999+
return workingVersion.getDataset().getDatasetType().getLicenses();
6000+
}
6001+
return licenseServiceBean.listAllActive();
6002+
}
59956003

59966004
public String getJsonLd() {
59976005
if (isThisLatestReleasedVersion()) {

src/main/java/edu/harvard/iq/dataverse/api/Datasets.java

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
103103
import edu.harvard.iq.dataverse.dataset.DatasetType;
104104
import edu.harvard.iq.dataverse.dataset.DatasetTypeServiceBean;
105+
import edu.harvard.iq.dataverse.license.License;
105106

106107
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
107108
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
@@ -5323,11 +5324,47 @@ public Response addDatasetType(@Context ContainerRequestContext crc, String json
53235324
if (jsonIn == null || jsonIn.isEmpty()) {
53245325
return error(BAD_REQUEST, "JSON input was null or empty!");
53255326
}
5326-
5327+
53275328
String nameIn = null;
5329+
5330+
JsonArrayBuilder datasetTypesAfter = Json.createArrayBuilder();
5331+
List<MetadataBlock> metadataBlocksToSave = new ArrayList<>();
5332+
List<License> licensesToSave = new ArrayList<>();
5333+
53285334
try {
5329-
JsonObject jsonObject = JsonUtil.getJsonObject(jsonIn);
5330-
nameIn = jsonObject.getString("name", null);
5335+
JsonObject datasetTypeObj = JsonUtil.getJsonObject(jsonIn);
5336+
nameIn = datasetTypeObj.getString("name");
5337+
5338+
JsonArray arr = datasetTypeObj.getJsonArray("linkedMetadataBlocks");
5339+
if (arr != null && !arr.isEmpty()) {
5340+
for (JsonString jsonValue : arr.getValuesAs(JsonString.class)) {
5341+
String name = jsonValue.getString();
5342+
MetadataBlock metadataBlock = metadataBlockSvc.findByName(name);
5343+
if (metadataBlock != null) {
5344+
metadataBlocksToSave.add(metadataBlock);
5345+
datasetTypesAfter.add(name);
5346+
} else {
5347+
String availableBlocks = metadataBlockSvc.listMetadataBlocks().stream().map(MetadataBlock::getName).collect(Collectors.joining(", "));
5348+
return badRequest("Metadata block not found: " + name + ". Available metadata blocks: " + availableBlocks);
5349+
}
5350+
}
5351+
}
5352+
5353+
arr = datasetTypeObj.getJsonArray("availableLicenses");
5354+
if (arr != null && !arr.isEmpty()) {
5355+
for (JsonString jsonValue : arr.getValuesAs(JsonString.class)) {
5356+
String name = jsonValue.getString();
5357+
License license = licenseSvc.getByNameOrUri(name);
5358+
if (license != null) {
5359+
licensesToSave.add(license);
5360+
} else {
5361+
String availableLicenses = licenseSvc.listAllActive().stream().map(License::getName).collect(Collectors.joining(", "));
5362+
return badRequest("License not found: " + name + ". Available licenses: " + availableLicenses);
5363+
}
5364+
}
5365+
5366+
}
5367+
53315368
} catch (JsonParsingException ex) {
53325369
return error(BAD_REQUEST, "Problem parsing supplied JSON: " + ex.getLocalizedMessage());
53335370
}
@@ -5342,6 +5379,8 @@ public Response addDatasetType(@Context ContainerRequestContext crc, String json
53425379
try {
53435380
DatasetType datasetType = new DatasetType();
53445381
datasetType.setName(nameIn);
5382+
datasetType.setMetadataBlocks(metadataBlocksToSave);
5383+
datasetType.setLicenses(licensesToSave);
53455384
DatasetType saved = datasetTypeSvc.save(datasetType);
53465385
Long typeId = saved.getId();
53475386
String name = saved.getName();
@@ -5444,6 +5483,54 @@ public Response updateDatasetTypeLinksWithMetadataBlocks(@Context ContainerReque
54445483
return ex.getResponse();
54455484
}
54465485
}
5486+
5487+
@AuthRequired
5488+
@PUT
5489+
@Path("datasetTypes/{idOrName}/licenses")
5490+
public Response updateDatasetTypeWithLicenses(@Context ContainerRequestContext crc, @PathParam("idOrName") String idOrName, String jsonBody) {
5491+
DatasetType datasetType = null;
5492+
if (StringUtils.isNumeric(idOrName)) {
5493+
try {
5494+
long id = Long.parseLong(idOrName);
5495+
datasetType = datasetTypeSvc.getById(id);
5496+
} catch (NumberFormatException ex) {
5497+
return error(NOT_FOUND, "Could not find a dataset type with id " + idOrName);
5498+
}
5499+
} else {
5500+
datasetType = datasetTypeSvc.getByName(idOrName);
5501+
}
5502+
JsonArrayBuilder licensesBefore = Json.createArrayBuilder();
5503+
for (License license : datasetType.getLicenses()) {
5504+
licensesBefore.add(license.getName());
5505+
}
5506+
JsonArrayBuilder licensesAfter = Json.createArrayBuilder();
5507+
List<License> licensesToSave = new ArrayList<>();
5508+
if (jsonBody != null && !jsonBody.isEmpty()) {
5509+
JsonArray json = JsonUtil.getJsonArray(jsonBody);
5510+
for (JsonString jsonValue : json.getValuesAs(JsonString.class)) {
5511+
String name = jsonValue.getString();
5512+
License license = licenseSvc.getByNameOrUri(name);
5513+
if (license != null) {
5514+
licensesToSave.add(license);
5515+
licensesAfter.add(name);
5516+
} else {
5517+
String availableLicenses = licenseSvc.listAllActive().stream().map(License::getName).collect(Collectors.joining(", "));
5518+
return badRequest("License not found: " + name + ". Available licenses: " + availableLicenses);
5519+
}
5520+
}
5521+
}
5522+
try {
5523+
execCommand(new UpdateDatasetTypeAvailableLicensesCommand(createDataverseRequest(getRequestUser(crc)), datasetType, licensesToSave));
5524+
return ok(Json.createObjectBuilder()
5525+
.add("availableLicenses", Json.createObjectBuilder()
5526+
.add("before", licensesBefore)
5527+
.add("after", licensesAfter))
5528+
);
5529+
5530+
} catch (WrappedResponse ex) {
5531+
return ex.getResponse();
5532+
}
5533+
}
54475534

54485535
@PUT
54495536
@AuthRequired

src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package edu.harvard.iq.dataverse.dataset;
22

33
import edu.harvard.iq.dataverse.MetadataBlock;
4+
import edu.harvard.iq.dataverse.license.License;
45
import jakarta.json.Json;
56
import jakarta.json.JsonArrayBuilder;
67
import jakarta.json.JsonObjectBuilder;
@@ -53,6 +54,12 @@ public class DatasetType implements Serializable {
5354
*/
5455
@ManyToMany(cascade = {CascadeType.MERGE})
5556
private List<MetadataBlock> metadataBlocks = new ArrayList<>();
57+
58+
/**
59+
* The Licenses this dataset type is linked to.
60+
*/
61+
@ManyToMany(cascade = {CascadeType.MERGE})
62+
private List<License> licenses = new ArrayList<>();
5663

5764
public DatasetType() {
5865
}
@@ -80,16 +87,29 @@ public List<MetadataBlock> getMetadataBlocks() {
8087
public void setMetadataBlocks(List<MetadataBlock> metadataBlocks) {
8188
this.metadataBlocks = metadataBlocks;
8289
}
90+
91+
public List<License> getLicenses() {
92+
return licenses;
93+
}
94+
95+
public void setLicenses(List<License> licenses) {
96+
this.licenses = licenses;
97+
}
8398

8499
public JsonObjectBuilder toJson() {
85100
JsonArrayBuilder linkedMetadataBlocks = Json.createArrayBuilder();
86101
for (MetadataBlock metadataBlock : this.getMetadataBlocks()) {
87102
linkedMetadataBlocks.add(metadataBlock.getName());
88103
}
104+
JsonArrayBuilder availableLicenses = Json.createArrayBuilder();
105+
for (License license : this.getLicenses()) {
106+
availableLicenses.add(license.getName());
107+
}
89108
return Json.createObjectBuilder()
90109
.add("id", getId())
91110
.add("name", getName())
92-
.add("linkedMetadataBlocks", linkedMetadataBlocks);
111+
.add("linkedMetadataBlocks", linkedMetadataBlocks)
112+
.add("availableLicenses", availableLicenses);
93113
}
94114

95115
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package edu.harvard.iq.dataverse.engine.command.impl;
2+
3+
import edu.harvard.iq.dataverse.DvObject;
4+
import edu.harvard.iq.dataverse.MetadataBlock;
5+
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
6+
import edu.harvard.iq.dataverse.dataset.DatasetType;
7+
import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand;
8+
import edu.harvard.iq.dataverse.engine.command.CommandContext;
9+
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
10+
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
11+
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
12+
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
13+
import edu.harvard.iq.dataverse.license.License;
14+
import java.util.List;
15+
16+
@RequiredPermissions({})
17+
public class UpdateDatasetTypeAvailableLicensesCommand extends AbstractVoidCommand {
18+
19+
final DatasetType datasetType;
20+
List<License> licenses;
21+
22+
public UpdateDatasetTypeAvailableLicensesCommand(DataverseRequest dataverseRequest, DatasetType datasetType, List<License> licenses) {
23+
super(dataverseRequest, (DvObject) null);
24+
this.datasetType = datasetType;
25+
this.licenses = licenses;
26+
}
27+
28+
@Override
29+
protected void executeImpl(CommandContext ctxt) throws CommandException {
30+
if (!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser()) {
31+
throw new PermissionException("Update dataset type links to metadata block command can only be called by superusers.",
32+
this, null, null);
33+
}
34+
datasetType.setLicenses(licenses);
35+
ctxt.em().merge(datasetType);
36+
}
37+
38+
}

0 commit comments

Comments
 (0)