Repository: IQSS/dataverse
Pull request #11385: 10520 dataset type licenses
Author: login: sekmiller
Labels: name: Type: Feature
description: a feature request
name: Size: 10
description: A percentage of a sprint. 7 hours.
name: GREI 6
description: Connect Digital Objects
name: GREI Year 3
description: Year 3 GREI task
name: FY25 Sprint 19
description: FY25 Sprint 19 (2025-03-12 - 2025-03-26)
name: FY25 Sprint 20
description: FY25 Sprint 20 (2025-03-26 - 2025-04-09)
name: FY25 Sprint 21
description: FY25 Sprint 21 (2025-04-09 - 2025-04-23)
Created: 2025-04-01T17:46:17Z | Merged: 2025-04-17T17:41:48Z

**What this PR does / why we need it**: Adds the ability to set licenses available to dataset types as a subset of the licenses available at the installation level. Part of GREI implementation

**Which issue(s) this PR closes**:

- Closes #10520 

**Special notes for your reviewer**:
Not much UI action, though I added a filter on the edit dataset terms for when there may be limited licenses available.

**Suggestions on how to test this**:
Add a dataset type via superuser api. Licenses can be added at create or by update api. Datasets added via api with a dataset type must have a license that is available for that dataset type. If not the license will be set to null. In the edit terms UI the only available licenses should be those available to the dataset type. If the type does not define available licenses then all active licenses should be available.

**Does this PR introduce a user interface change? If mockups are available, please link/include them here**:
On edit dataset terms the licenses will be limited to those defined for the given dataset type of the dataset being edited.

**Is there a release notes update needed for this change?**:
included
**Additional documentation**:
Docs updated to reflect the changes.

Linked issue #10520: Implement list of allowed datasetType licenses (at global level)
## Overview
- See https://github.com/IQSS/dataverse-pm/issues/307 and [Proposal: Supporting Multiple Dataset Types in Dataverse](https://docs.google.com/document/d/16RvGXmaPQK9DGsEEbrrFEu8mjUrN93YY_yh7ZONyMDI/edit?usp=sharing) for details

## Scope of Work
- Designate specific license options for a specific `datasetType`

## Depends on
- https://github.com/IQSS/dataverse/pull/10694
- https://github.com/IQSS/dataverse/issues/10519

## See also
- https://github.com/IQSS/dataverse-pm/issues/307
Linked issue comments: user: login: pdurbin
created_at: 2025-03-26T19:26:23Z
body: I chatted a bit with @doigl and @JR-1991 about this issue starting here: [#dev > datasetType (software, workflow, etc.) @ 💬](https://dataverse.zulipchat.com/#narrow/channel/379673-dev/topic/datasetType.20.28software.2C.20workflow.2C.20etc.2E.29/near/502063885)
author_association: MEMBER

Diff:
diff --git a/doc/release-notes/10520-datasetType-enable-licenses.md b/doc/release-notes/10520-datasetType-enable-licenses.md
new file mode 100644
index 00000000000..b434b045f70
--- /dev/null
+++ b/doc/release-notes/10520-datasetType-enable-licenses.md
@@ -0,0 +1,8 @@
+## Dataset Types can set available Licenses
+
+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. 
+
+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.
+Also, the UI will only show those licenses that are available to a the dataset's dataset type.
+
+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.
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index e0a57557d7f..95041ab5304 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -3645,13 +3645,13 @@ Add Dataset Type
 
 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.
 
-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.
+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.
 
 .. code-block:: bash
 
   export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
   export SERVER_URL=https://demo.dataverse.org
-  export JSON='{"name": "software"}'
+  export JSON='{"name":"software","linkedMetadataBlocks":["codeMeta20"],"availableLicenses":["MIT", "Apache-2.0"]}'
 
   curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes" -X POST -d $JSON
 
@@ -3659,7 +3659,7 @@ The fully expanded example above (without environment variables) looks like this
 
 .. code-block:: bash
 
-  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"}'
+  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"]}'
 
 .. _api-delete-dataset-type:
 
@@ -3712,6 +3712,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "MIT" and "Apache-2.0".
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["MIT", "Apache-2.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["MIT", "Apache-2.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
+
 .. _api-dataset-version-note:
 
 Dataset Version Notes
diff --git a/scripts/api/data/dataset-create-new-with-type.json b/scripts/api/data/dataset-create-new-with-type.json
new file mode 100644
index 00000000000..5369151eea4
--- /dev/null
+++ b/scripts/api/data/dataset-create-new-with-type.json
@@ -0,0 +1,82 @@
+{
+  "datasetType": "testDatasetType",
+  "datasetVersion": {
+    "license": {
+      "name": "CC BY 4.0",
+      "uri": "https://creativecommons.org/licenses/by/4.0/"
+    },
+    "metadataBlocks": {
+      "citation": {
+        "fields": [
+          {
+            "value": "pyDataverse",
+            "typeClass": "primitive",
+            "multiple": false,
+            "typeName": "title"
+          },
+          {
+            "value": [
+              {
+                "authorName": {
+                  "value": "Range, Dan",
+                  "typeClass": "primitive",
+                  "multiple": false,
+                  "typeName": "authorName"
+                },
+                "authorAffiliation": {
+                  "value": "University of Stuttgart",
+                  "typeClass": "primitive",
+                  "multiple": false,
+                  "typeName": "authorAffiliation"
+                }
+              }
+            ],
+            "typeClass": "compound",
+            "multiple": true,
+            "typeName": "author"
+          },
+          {
+            "value": [ 
+                { "datasetContactEmail" : {
+                    "typeClass": "primitive",
+                    "multiple": false,
+                    "typeName": "datasetContactEmail",
+                    "value" : "jan@mailinator.com"
+                },
+                "datasetContactName" : {
+                    "typeClass": "primitive",
+                    "multiple": false,
+                    "typeName": "datasetContactName",
+                    "value": "Range, Jan"
+                }
+            }],
+            "typeClass": "compound",
+            "multiple": true,
+            "typeName": "datasetContact"
+          },
+          {
+            "value": [ {
+               "dsDescriptionValue":{
+                "value": "A Python module for Dataverse.",
+                "multiple":false,
+               "typeClass": "primitive",
+               "typeName": "dsDescriptionValue"
+            }}],
+            "typeClass": "compound",
+            "multiple": true,
+            "typeName": "dsDescription"
+          },
+          {
+            "value": [
+              "Computer and Information Science"
+            ],
+            "typeClass": "controlledVocabulary",
+            "multiple": true,
+            "typeName": "subject"
+          }
+        ],
+        "displayName": "Citation Metadata"
+      }
+    }
+  }
+}
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
index af8cdc21968..9e9273ade65 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
@@ -133,6 +133,7 @@
 import edu.harvard.iq.dataverse.globus.GlobusServiceBean;
 import edu.harvard.iq.dataverse.export.SchemaDotOrgExporter;
 import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler;
+import edu.harvard.iq.dataverse.license.License;
 import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean;
 import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry;
 import java.util.Collections;
@@ -5992,6 +5993,13 @@ public String getCroissant() {
         }
         return null;
     }
+    
+    public List<License> getAvailableLicenses(){
+        if(!workingVersion.getDataset().getDatasetType().getLicenses().isEmpty()){
+            return workingVersion.getDataset().getDatasetType().getLicenses();
+        }
+        return licenseServiceBean.listAllActive();
+    }
 
     public String getJsonLd() {
         if (isThisLatestReleasedVersion()) {
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index 3f0e5e42658..bf04cdd6226 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -102,6 +102,7 @@
 import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
 import edu.harvard.iq.dataverse.dataset.DatasetType;
 import edu.harvard.iq.dataverse.dataset.DatasetTypeServiceBean;
+import edu.harvard.iq.dataverse.license.License;
 
 import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
 import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
@@ -5323,11 +5324,47 @@ public Response addDatasetType(@Context ContainerRequestContext crc, String json
         if (jsonIn == null || jsonIn.isEmpty()) {
             return error(BAD_REQUEST, "JSON input was null or empty!");
         }
-
+        
         String nameIn = null;
+        
+        JsonArrayBuilder datasetTypesAfter = Json.createArrayBuilder();
+        List<MetadataBlock> metadataBlocksToSave = new ArrayList<>();
+        List<License> licensesToSave = new ArrayList<>();
+        
         try {
-            JsonObject jsonObject = JsonUtil.getJsonObject(jsonIn);
-            nameIn = jsonObject.getString("name", null);
+            JsonObject datasetTypeObj =  JsonUtil.getJsonObject(jsonIn);
+            nameIn = datasetTypeObj.getString("name");
+            
+            JsonArray arr = datasetTypeObj.getJsonArray("linkedMetadataBlocks");
+            if (arr != null && !arr.isEmpty()) {
+                for (JsonString jsonValue : arr.getValuesAs(JsonString.class)) {
+                    String name = jsonValue.getString();
+                    MetadataBlock metadataBlock = metadataBlockSvc.findByName(name);
+                    if (metadataBlock != null) {
+                        metadataBlocksToSave.add(metadataBlock);
+                        datasetTypesAfter.add(name);
+                    } else {
+                        String availableBlocks = metadataBlockSvc.listMetadataBlocks().stream().map(MetadataBlock::getName).collect(Collectors.joining(", "));
+                        return badRequest("Metadata block not found: " + name + ". Available metadata blocks: " + availableBlocks);
+                    }
+                }
+            }
+
+            arr = datasetTypeObj.getJsonArray("availableLicenses");
+            if (arr != null && !arr.isEmpty()) {
+                for (JsonString jsonValue : arr.getValuesAs(JsonString.class)) {
+                    String name = jsonValue.getString();
+                    License license = licenseSvc.getByNameOrUri(name);
+                    if (license != null) {
+                        licensesToSave.add(license);
+                    } else {
+                        String availableLicenses = licenseSvc.listAllActive().stream().map(License::getName).collect(Collectors.joining(", "));
+                        return badRequest("License not found: " + name + ". Available licenses: " + availableLicenses);
+                    }
+                }
+
+            }
+
         } catch (JsonParsingException ex) {
             return error(BAD_REQUEST, "Problem parsing supplied JSON: " + ex.getLocalizedMessage());
         }
@@ -5342,6 +5379,8 @@ public Response addDatasetType(@Context ContainerRequestContext crc, String json
         try {
             DatasetType datasetType = new DatasetType();
             datasetType.setName(nameIn);
+            datasetType.setMetadataBlocks(metadataBlocksToSave);
+            datasetType.setLicenses(licensesToSave);
             DatasetType saved = datasetTypeSvc.save(datasetType);
             Long typeId = saved.getId();
             String name = saved.getName();
@@ -5444,6 +5483,54 @@ public Response updateDatasetTypeLinksWithMetadataBlocks(@Context ContainerReque
             return ex.getResponse();
         }
     }
+    
+    @AuthRequired
+    @PUT
+    @Path("datasetTypes/{idOrName}/licenses")
+    public Response updateDatasetTypeWithLicenses(@Context ContainerRequestContext crc, @PathParam("idOrName") String idOrName, String jsonBody) {
+        DatasetType datasetType = null;
+        if (StringUtils.isNumeric(idOrName)) {
+            try {
+                long id = Long.parseLong(idOrName);
+                datasetType = datasetTypeSvc.getById(id);
+            } catch (NumberFormatException ex) {
+                return error(NOT_FOUND, "Could not find a dataset type with id " + idOrName);
+            }
+        } else {
+            datasetType = datasetTypeSvc.getByName(idOrName);
+        }
+        JsonArrayBuilder licensesBefore = Json.createArrayBuilder();
+        for (License license : datasetType.getLicenses()) {
+            licensesBefore.add(license.getName());
+        }
+        JsonArrayBuilder licensesAfter = Json.createArrayBuilder();
+        List<License> licensesToSave = new ArrayList<>();
+        if (jsonBody != null && !jsonBody.isEmpty()) {
+            JsonArray json = JsonUtil.getJsonArray(jsonBody);
+            for (JsonString jsonValue : json.getValuesAs(JsonString.class)) {
+                String name = jsonValue.getString();
+                License license = licenseSvc.getByNameOrUri(name);
+                if (license != null) {
+                    licensesToSave.add(license);
+                    licensesAfter.add(name);
+                } else {
+                    String availableLicenses = licenseSvc.listAllActive().stream().map(License::getName).collect(Collectors.joining(", "));
+                    return badRequest("License not found: " + name + ". Available licenses: " + availableLicenses);
+                }
+            }
+        }
+        try {
+            execCommand(new UpdateDatasetTypeAvailableLicensesCommand(createDataverseRequest(getRequestUser(crc)), datasetType, licensesToSave));
+            return ok(Json.createObjectBuilder()
+                    .add("availableLicenses", Json.createObjectBuilder()
+                            .add("before", licensesBefore)
+                            .add("after", licensesAfter))
+            );
+
+        } catch (WrappedResponse ex) {
+            return ex.getResponse();
+        }
+    }
 
     @PUT
     @AuthRequired
diff --git a/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java b/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
index 727703852eb..c55324f66e3 100644
--- a/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
+++ b/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
@@ -1,6 +1,7 @@
 package edu.harvard.iq.dataverse.dataset;
 
 import edu.harvard.iq.dataverse.MetadataBlock;
+import edu.harvard.iq.dataverse.license.License;
 import jakarta.json.Json;
 import jakarta.json.JsonArrayBuilder;
 import jakarta.json.JsonObjectBuilder;
@@ -53,6 +54,12 @@ public class DatasetType implements Serializable {
      */
     @ManyToMany(cascade = {CascadeType.MERGE})
     private List<MetadataBlock> metadataBlocks = new ArrayList<>();
+    
+    /**
+     * The Licenses this dataset type is linked to.
+     */
+    @ManyToMany(cascade = {CascadeType.MERGE})
+    private List<License> licenses = new ArrayList<>();
 
     public DatasetType() {
     }
@@ -80,16 +87,29 @@ public List<MetadataBlock> getMetadataBlocks() {
     public void setMetadataBlocks(List<MetadataBlock> metadataBlocks) {
         this.metadataBlocks = metadataBlocks;
     }
+    
+    public List<License> getLicenses() {
+        return licenses;
+    }
+
+    public void setLicenses(List<License> licenses) {
+        this.licenses = licenses;
+    }
 
     public JsonObjectBuilder toJson() {
         JsonArrayBuilder linkedMetadataBlocks = Json.createArrayBuilder();
         for (MetadataBlock metadataBlock : this.getMetadataBlocks()) {
             linkedMetadataBlocks.add(metadataBlock.getName());
         }
+        JsonArrayBuilder availableLicenses = Json.createArrayBuilder();
+        for (License license : this.getLicenses()) {
+            availableLicenses.add(license.getName());
+        }
         return Json.createObjectBuilder()
                 .add("id", getId())
                 .add("name", getName())
-                .add("linkedMetadataBlocks", linkedMetadataBlocks);
+                .add("linkedMetadataBlocks", linkedMetadataBlocks)
+                .add("availableLicenses", availableLicenses);
     }
 
 }
diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeAvailableLicensesCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeAvailableLicensesCommand.java
new file mode 100644
index 00000000000..b2dcd6cf33e
--- /dev/null
+++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeAvailableLicensesCommand.java
@@ -0,0 +1,38 @@
+package edu.harvard.iq.dataverse.engine.command.impl;
+
+import edu.harvard.iq.dataverse.DvObject;
+import edu.harvard.iq.dataverse.MetadataBlock;
+import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
+import edu.harvard.iq.dataverse.dataset.DatasetType;
+import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand;
+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.engine.command.exception.PermissionException;
+import edu.harvard.iq.dataverse.license.License;
+import java.util.List;
+
+@RequiredPermissions({})
+public class UpdateDatasetTypeAvailableLicensesCommand extends AbstractVoidCommand {
+
+    final DatasetType datasetType;
+    List<License> licenses;
+
+    public UpdateDatasetTypeAvailableLicensesCommand(DataverseRequest dataverseRequest, DatasetType datasetType, List<License> licenses) {
+        super(dataverseRequest, (DvObject) null);
+        this.datasetType = datasetType;
+        this.licenses = licenses;
+    }
+
+    @Override
+    protected void executeImpl(CommandContext ctxt) throws CommandException {
+        if (!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser()) {
+            throw new PermissionException("Update dataset type links to metadata block command can only be called by superusers.",
+                    this, null, null);
+        }
+        datasetType.setLicenses(licenses);
+        ctxt.em().merge(datasetType);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
index cc051d59ba0..e5be6f9edc2 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
@@ -464,6 +464,24 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th
                 // "license" : "CC0 1.0"
                 license = parseLicense(obj.getString("license", null));
             }
+            
+            //test to see if license exists in dataset type 
+            //if not set it to null - 
+            //only test if Dataset has a type and if it has custom available licenses
+            if (dsv.getDataset() != null) {
+                DatasetType dst = dsv.getDataset().getDatasetType();
+                if (dst != null && dst.getLicenses() != null && !dst.getLicenses().isEmpty() && license != null) {
+                    boolean invalidLicense = true;
+                    for (License testLicense : dst.getLicenses()) {
+                        if (testLicense.equals(license)) {
+                            invalidLicense = false;
+                        }
+                    }
+                    if (invalidLicense) {
+                        license = null;
+                    }
+                }
+            }           
 
             if (license == null) {
                 terms.setLicense(license);
diff --git a/src/main/webapp/dataset-license-terms.xhtml b/src/main/webapp/dataset-license-terms.xhtml
index 03173faf989..af055b13899 100644
--- a/src/main/webapp/dataset-license-terms.xhtml
+++ b/src/main/webapp/dataset-license-terms.xhtml
@@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
+                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}" 
+                                                   var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
+                                       <c:if test="#{datasetPage}">
+                                       <f:selectItems value="#{DatasetPage.availableLicenses}" 
                                                    var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
                                        <c:if test="#{settingsWrapper.customLicenseAllowed}">
                                            <f:selectItem itemLabel="#{bundle['license.custom']}" itemValue="#{null}"/>
                                        </c:if>
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java
index 9c4ad6ffbbd..205725822ff 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java
@@ -5,17 +5,20 @@
 import io.restassured.path.json.JsonPath;
 import io.restassured.response.Response;
 import jakarta.json.Json;
+import jakarta.json.JsonObjectBuilder;
 import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
 import static jakarta.ws.rs.core.Response.Status.CREATED;
 import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
 import static jakarta.ws.rs.core.Response.Status.OK;
 import java.util.UUID;
 import org.hamcrest.CoreMatchers;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import org.junit.jupiter.api.AfterAll;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
@@ -277,6 +280,116 @@ public void testAddAndDeleteDatasetType() {
         typeDeleted.then().assertThat().statusCode(OK.getStatusCode());
 
     }
+    
+    @Test
+    public void testAddDatasetTypeWithMDBLicense(){
+        
+        Response createUser = UtilIT.createRandomUser();
+        createUser.then().assertThat().statusCode(OK.getStatusCode());
+        String username = UtilIT.getUsernameFromResponse(createUser);
+        String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+        UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode());
+
+        JsonObjectBuilder job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+        job.add("linkedMetadataBlocks", Json.createArrayBuilder().add("geospatial"));
+        job.add("availableLicenses", Json.createArrayBuilder().add("CC0 1.0"));
+
+        Response typeAdded = UtilIT.addDatasetType(job.build(), apiToken);
+        typeAdded.prettyPrint();
+
+        typeAdded.then().assertThat().statusCode(OK.getStatusCode());
+
+        Long doomed = JsonPath.from(typeAdded.getBody().asString()).getLong("data.id");
+
+        System.out.println("doomed: " + doomed);
+        Response getTypeById = UtilIT.getDatasetType(doomed.toString());
+        getTypeById.prettyPrint();
+        getTypeById.then().assertThat().statusCode(OK.getStatusCode());
+
+        System.out.println("deleting type with id " + doomed);
+        Response typeDeleted = UtilIT.deleteDatasetTypes(doomed, apiToken);
+        typeDeleted.prettyPrint();
+        typeDeleted.then().assertThat().statusCode(OK.getStatusCode());
+        
+        //bad metadatablock name 
+        job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+        job.add("linkedMetadataBlocks", Json.createArrayBuilder().add("geospatialXXX"));
+        job.add("availableLicenses", Json.createArrayBuilder().add("CC0 1.0"));
+        
+        typeAdded = UtilIT.addDatasetType(job.build(), apiToken);
+        typeAdded.prettyPrint();
+
+        typeAdded.then().assertThat()
+                .statusCode(BAD_REQUEST.getStatusCode())
+                .body("message", containsString("Metadata block not found:"));
+        
+        job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+        job.add("linkedMetadataBlocks", Json.createArrayBuilder().add("geospatial"));
+        job.add("availableLicenses", Json.createArrayBuilder().add("CC0 12.0"));
+
+        typeAdded = UtilIT.addDatasetType(job.build(), apiToken); 
+        typeAdded.prettyPrint();
+        typeAdded.then().assertThat()
+                .statusCode(BAD_REQUEST.getStatusCode())
+                .body("message", containsString("License not found:"));
+
+    }
+    
+    @Test
+    public void testUpdateDatasetTypeWithLicense(){
+        
+        Response createUser = UtilIT.createRandomUser();
+        createUser.then().assertThat().statusCode(OK.getStatusCode());
+        String username = UtilIT.getUsernameFromResponse(createUser);
+        String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+        UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode());
+
+        JsonObjectBuilder job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+
+        Response typeAdded = UtilIT.addDatasetType(job.build(), apiToken);
+        typeAdded.prettyPrint();
+
+        typeAdded.then().assertThat().statusCode(OK.getStatusCode());
+        
+                String availableLicenseArray = """
+            ["CC0 1.0"]
+""";
+        
+        Response addCC0 = UtilIT.updateDatasetTypeAvailableLicense("testDatasetType", availableLicenseArray, apiToken);
+        addCC0.prettyPrint();
+        addCC0.then().assertThat().
+                statusCode(OK.getStatusCode())
+                .body("data.availableLicenses.after[0]", CoreMatchers.is("CC0 1.0"));
+        
+        
+                        String badAvailableLicenseArray = """
+            ["CC0 xx.0"]
+""";
+
+        addCC0 = UtilIT.updateDatasetTypeAvailableLicense("testDatasetType", badAvailableLicenseArray, apiToken);
+        addCC0.prettyPrint();
+        addCC0.then().assertThat().
+                statusCode(BAD_REQUEST.getStatusCode())
+                .body("message", containsString("License not found:"));
+        
+                        
+        Long doomed = JsonPath.from(typeAdded.getBody().asString()).getLong("data.id");
+
+        System.out.println("doomed: " + doomed);
+        Response getTypeById = UtilIT.getDatasetType(doomed.toString());
+        getTypeById.prettyPrint();
+        getTypeById.then().assertThat().statusCode(OK.getStatusCode());
+
+        System.out.println("deleting type with id " + doomed);
+        Response typeDeleted = UtilIT.deleteDatasetTypes(doomed, apiToken);
+        typeDeleted.prettyPrint();
+        typeDeleted.then().assertThat().statusCode(OK.getStatusCode());
+
+    }
 
     @Test
     public void testUpdateDatasetTypeLinksWithMetadataBlocks() {
@@ -410,6 +523,31 @@ public void testUpdateDatasetTypeLinksWithMetadataBlocks() {
         listBlocks.prettyPrint();
         listBlocks.then().assertThat()
                 .body("data[0].name", is("citation"));
+        
+        //Cleanup objects 
+
+        
+        Long doomed = JsonPath.from(typeAdded.getBody().asString()).getLong("data.id");
+
+        System.out.println("doomed: " + doomed);
+
+        getTypeById.prettyPrint();
+        getTypeById.then().assertThat().statusCode(OK.getStatusCode());
+
+        System.out.println("deleting type with id " + doomed);
+        Response typeDeleted = UtilIT.deleteDatasetTypes(doomed, apiToken);
+        typeDeleted.prettyPrint();
+        typeDeleted.then().assertThat().statusCode(OK.getStatusCode());
+        
+        Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken);
+        deleteDataverseResponse.prettyPrint();
+        assertEquals(200, deleteDataverseResponse.getStatusCode());
+
+        Response deleteUserResponse = UtilIT.deleteUser(username);
+        deleteUserResponse.prettyPrint();
+        assertEquals(200, deleteUserResponse.getStatusCode());
+        
+        
     }
 
     @Test
@@ -497,5 +635,82 @@ public void testLinkInstrumentToAstro() {
                 .body("data[1].fields.astroObject.displayOnCreate", equalTo(false));
 
     }
+    
+    @Test
+    public void testCreateDatasetWithCustomType() {
+        Response createUser = UtilIT.createRandomUser();
+        createUser.then().assertThat().statusCode(OK.getStatusCode());
+        String username = UtilIT.getUsernameFromResponse(createUser);
+        String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+
+        Response createDataverse = UtilIT.createRandomDataverse(apiToken);
+        createDataverse.then().assertThat().statusCode(CREATED.getStatusCode());
+        String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse);
+        Integer dataverseId = UtilIT.getDataverseIdFromResponse(createDataverse);
+       
+        UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode());
+   
+        JsonObjectBuilder job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+        job.add("linkedMetadataBlocks", Json.createArrayBuilder().add("geospatial"));
+        job.add("availableLicenses", Json.createArrayBuilder().add("CC0 1.0"));
+        
+        Response typeAdded = UtilIT.addDatasetType(job.build(), apiToken);
+        typeAdded.prettyPrint();
+
+        typeAdded.then().assertThat().statusCode(OK.getStatusCode());
+        Response getTypes = UtilIT.getDatasetTypes();   
+        getTypes = UtilIT.getDatasetTypes();
+        getTypes.prettyPrint();
+
+        String pathToJsonFile = "scripts/api/data/dataset-create-new-with-type.json";
+        
+        Response createDatasetResponse = UtilIT.createDatasetViaNativeApi(dataverseAlias, pathToJsonFile, apiToken);
+        
+        createDatasetResponse.prettyPrint();
+        
+
+        createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode());
+
+        Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse);
+        String datasetPid = JsonPath.from(createDatasetResponse.getBody().asString()).getString("data.persistentId");
+
+        Response getDatasetJson = UtilIT.nativeGet(datasetId, apiToken);
+        getDatasetJson.prettyPrint();
+        getDatasetJson.then().assertThat().statusCode(OK.getStatusCode());
+        String datasetType = JsonPath.from(getDatasetJson.getBody().asString()).getString("data.datasetType");
+        System.out.println("datasetType: " + datasetType);
+        assertEquals("testDatasetType", datasetType);
+        String datasetVerstionState = JsonPath.from(getDatasetJson.getBody().asString()).getString("data.latestVersion.versionState");
+        assertEquals("DRAFT", datasetVerstionState);
+        String datasetLicense = JsonPath.from(getDatasetJson.getBody().asString()).getString("data.latestVersion.license");
+        //License is null because the one in the dataset json does not correspond to the 
+        // available licenses in the dataset type.
+        assertNull(datasetLicense);
+        Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, apiToken);
+        deleteDatasetResponse.prettyPrint();
+        assertEquals(200, deleteDatasetResponse.getStatusCode());
+        
+        Long doomed = JsonPath.from(typeAdded.getBody().asString()).getLong("data.id");
+
+        System.out.println("doomed: " + doomed);
+        Response getTypeById = UtilIT.getDatasetType(doomed.toString());
+        getTypeById.prettyPrint();
+        getTypeById.then().assertThat().statusCode(OK.getStatusCode());
+
+        System.out.println("deleting type with id " + doomed);
+        Response typeDeleted = UtilIT.deleteDatasetTypes(doomed, apiToken);
+        typeDeleted.prettyPrint();
+        typeDeleted.then().assertThat().statusCode(OK.getStatusCode());
+        
+        Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken);
+        deleteDataverseResponse.prettyPrint();
+        assertEquals(200, deleteDataverseResponse.getStatusCode());
+
+        Response deleteUserResponse = UtilIT.deleteUser(username);
+        deleteUserResponse.prettyPrint();
+        assertEquals(200, deleteUserResponse.getStatusCode());
+
+    }
 
 }
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
index 7733f04be04..bcac3a891dd 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -4491,6 +4491,17 @@ static Response addDatasetType(String jsonIn, String apiToken) {
                 .contentType(ContentType.JSON)
                 .post("/api/datasets/datasetTypes");
     }
+    
+    static Response addDatasetType(JsonObject jsonObject, String apiToken) {
+        RequestSpecification requestSpecification = given();
+        if (apiToken != null) {
+            requestSpecification.header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken);
+        }
+        return requestSpecification
+                .body(jsonObject.toString())
+                .contentType(ContentType.JSON)
+                .post("/api/datasets/datasetTypes");
+    }
 
     static Response deleteDatasetTypes(long doomed, String apiToken) {
         return given()
@@ -4504,6 +4515,13 @@ static Response updateDatasetTypeLinksWithMetadataBlocks(String idOrName, String
                 .body(jsonArrayOfMetadataBlocks)
                 .put("/api/datasets/datasetTypes/" + idOrName);
     }
+    
+    static Response updateDatasetTypeAvailableLicense(String idOrName, String jsonArrayOfLicenses, String apiToken) {
+        return given()
+                .header(API_TOKEN_HTTP_HEADER, apiToken)
+                .body(jsonArrayOfLicenses)
+                .put("/api/datasets/datasetTypes/" + idOrName + "/licenses");
+    }
 
     static Response registerOidcUser(String jsonIn, String bearerToken) {
         return given()


Reviews:
user: login: qqmyers
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T22:16:00Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:22:21Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:24:22Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:42:19Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:44:55Z
user: login: qqmyers
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T16:59:44Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T19:27:50Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T20:23:10Z
user: login: qqmyers
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T21:12:19Z
user: login: pdurbin
body: Overall this looks good. I left some feedback.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T19:32:06Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T20:01:30Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:43:26Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:44:20Z
user: login: sekmiller
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:58:13Z
user: login: pdurbin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T13:38:11Z
user: login: pdurbin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T15:40:45Z
user: login: pdurbin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T15:47:43Z
user: login: pdurbin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T17:53:25Z
user: login: pdurbin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T17:54:19Z
user: login: pdurbin
body: I think this is ready for QA so I'm approving this.

@sekmiller please keep an eye on the crazy `RemoteOverlayAccessIOTest.testRemoteOverlayFiles:103 expected: <true> but was: <false>` error I showed you: https://github.com/IQSS/dataverse/actions/runs/14496860036/job/40666682581?pr=11385 . I'm not sure why I'm seeing it. Update: @qqmyers wrote about this [in Slack](https://iqss.slack.com/archives/C03R1E7T4KA/p1744825616354749). Someone needs to switch from that test from https://data.qdr.syr.edu/resources to another server.

Recent API test runs on Jenkins have been passing. That's good.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T17:56:07Z

Review comments:
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: qqmyers
body: Do we have the use case where someone would want to require custom terms by not allowing any licenses for a given type? (i.e. no content would not mean delete?)
created_at: 2025-04-01T22:16:00Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: I don't know. I can ask the curation team.
created_at: 2025-04-02T13:22:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: @sbarbosadataverse, @jggautier - do we have any cases where we want to require the Dataset owner to put in Custom Terms?
created_at: 2025-04-02T13:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: Had a sidebar with Sonia and Julian. Sonia said that if there are no licenses available then Custom Terms would be required. (isn't allowing Custom Terms a setting or Feature Flag?) Anyway I will add a migration script that will assign the currently active licenses to any existing Dataset Type - from there the admin can use the api to limit or remove.
created_at: 2025-04-02T15:42:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: I'll also have to fix/update "getAvailableLicenses" and the doc, etc.
created_at: 2025-04-02T15:44:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: qqmyers
body: FWIW: I was thinking that instead of having no licenses mean delete that you actually allow that value and have a separate delete. Then the default would be a new type gets all the normal licenses unless you override per type. That would avoid duplicating db entries (and having types get out of sync if you add a new license, etc.)
created_at: 2025-04-02T16:59:44Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: OK. that makes sense. keeping up to date with available licenses would be a problem.
created_at: 2025-04-02T19:27:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: sekmiller
body: OK, so bottom line is that if we say that available  licenses for a type are depicted in the datasettype_license table then of a type has no records there we can assume that all active licenses are available for datasets of that type.
created_at: 2025-04-03T20:23:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: qqmyers
body: Yes, except I'm not sure that supports a way to say that a type should only allow custom terms/no licenses -  unless that table can have null entries (or there's some other boolean) to indicate that you shouldn't inherit all the active ones. Again, not sure if the design has to cover that.
created_at: 2025-04-03T21:12:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,8 @@
+## Dataset Types can set available Licenses
+
+Licenses (e.g. "CC0 1.0") 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. 
path: doc/release-notes/10520-datasetType-enable-licenses
user: login: pdurbin
body: ```suggestion
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. 
```
created_at: 2025-04-08T18:55:56Z
author_association: MEMBER
path: doc/release-notes/10520-datasetType-enable-licenses
user: login: pdurbin
body: If you rename this file to end in `.md` it will be previewable on GitHub.
created_at: 2025-04-08T18:56:52Z
author_association: MEMBER
diff_hunk: @@ -3619,7 +3619,7 @@ Add Dataset Type
 
 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.
 
-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.
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: We should show a JSON example of these.
created_at: 2025-04-08T18:58:02Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: ```suggestion
For example, a superuser could create a type called "software" and limit the available licenses to only "MIT" and "Apache-2.0".
```
created_at: 2025-04-08T18:58:54Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: ```suggestion
  export JSON='["MIT", "Apache-2.0"]'
```

I'm suggesting these software licenses because above we're talking about a type called "software".
created_at: 2025-04-08T19:00:07Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: ```suggestion
  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"]'
```

Same, because it's software.
created_at: 2025-04-08T19:00:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,8 @@
+## Dataset Types can set available Licenses
+
+Licenses (e.g. "CC0 1.0") 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. 
+
+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.
+Also, the UI will only show those licenses that are available to a the dataset's dataset type.
+
+For more information, see the guides ([overview](https://dataverse-guide--11001.org.readthedocs.build/en/11001/user/dataset-management.html#dataset-types), [new APIs](https://dataverse-guide--11001.org.readthedocs.build/en/11001/api/native-api.html#link-dataset-type-with-metadata-blocks)), #10519 and #11001.
path: doc/release-notes/10520-datasetType-enable-licenses
user: login: pdurbin
body: ```suggestion
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.
```
created_at: 2025-04-08T19:03:50Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: What's the status here?

Are we adding a separate DELETE?

Are custom terms supported?
created_at: 2025-04-08T19:06:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,82 @@
+{
+  "datasetType": "testDatasetType",
+  "datasetVersion": {
+    "license": {
+      "name": "CC BY 4.0",
+      "uri": "https://creativecommons.org/licenses/by/4.0/"
+    },
+    "metadataBlocks": {
+      "citation": {
+        "fields": [
+          {
+            "value": "pyDataverse",
+            "typeClass": "primitive",
+            "multiple": false,
+            "typeName": "title"
+          },
+          {
+            "value": [
+              {
+                "authorName": {
+                  "value": "Range, Dan",
path: scripts/api/data/dataset-create-new-with-type.json
user: login: pdurbin
body: Ha. Dan instead of Jan?
created_at: 2025-04-08T19:06:53Z
author_association: MEMBER
diff_hunk: @@ -53,6 +54,12 @@ public class DatasetType implements Serializable {
      */
     @ManyToMany(cascade = {CascadeType.MERGE})
     private List<MetadataBlock> metadataBlocks = new ArrayList<>();
+    
+    /**
+     * The Licenses this dataset type is linked to.
+     */
+    @ManyToMany(cascade = {CascadeType.MERGE})
+    private List<License> licenses = new ArrayList<>();
path: src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
user: login: pdurbin
body: Don't we need a Flyway script for this?
created_at: 2025-04-08T19:09:30Z
author_association: MEMBER
diff_hunk: @@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
path: src/main/webapp/dataset-license-terms.xhtml
user: login: pdurbin
body: I'm a little confused why this is here. Don't we always select licenses from the dataset page?
created_at: 2025-04-08T19:14:12Z
author_association: MEMBER
diff_hunk: @@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
+                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}" 
+                                                   var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
+                                       <c:if test="#{datasetPage}">
+                                       <f:selectItems value="#{DatasetPage.availableLicenses}" 
                                                    var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
path: src/main/webapp/dataset-license-terms.xhtml
user: login: pdurbin
body: How do I trigger this new availableLicenses logic in the JSF UI? We don't have any way to pass the dataset type in to JSF, do we?
created_at: 2025-04-08T19:16:38Z
author_association: MEMBER
diff_hunk: @@ -497,5 +635,82 @@ public void testLinkInstrumentToAstro() {
                 .body("data[1].fields.astroObject.displayOnCreate", equalTo(false));
 
     }
+    
+    @Test
+    public void testCreateDatasetWithCustomType() {
+        Response createUser = UtilIT.createRandomUser();
+        createUser.then().assertThat().statusCode(OK.getStatusCode());
+        String username = UtilIT.getUsernameFromResponse(createUser);
+        String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+
+        Response createDataverse = UtilIT.createRandomDataverse(apiToken);
+        createDataverse.then().assertThat().statusCode(CREATED.getStatusCode());
+        String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse);
+        Integer dataverseId = UtilIT.getDataverseIdFromResponse(createDataverse);
+       
+        UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode());
+   
+        JsonObjectBuilder job = Json.createObjectBuilder();
+        job.add("name", "testDatasetType");
+        job.add("linkedMetadataBlocks", Json.createArrayBuilder().add("geospatial"));
+        job.add("availableLicenses", Json.createArrayBuilder().add("CC0 1.0"));
+        
+        Response typeAdded = UtilIT.addDatasetType(job.build(), apiToken);
+        typeAdded.prettyPrint();
+
+        typeAdded.then().assertThat().statusCode(OK.getStatusCode());
+        Response getTypes = UtilIT.getDatasetTypes();   
+        getTypes = UtilIT.getDatasetTypes();
+        getTypes.prettyPrint();
+
+        String pathToJsonFile = "scripts/api/data/dataset-create-new-with-type.json";
+        
+        Response createDatasetResponse = UtilIT.createDatasetViaNativeApi(dataverseAlias, pathToJsonFile, apiToken);
+        
+        createDatasetResponse.prettyPrint();
+        
+
+        createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode());
path: src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java
user: login: pdurbin
body: I'm confused. Shouldn't this be a failure instead of CREATED? The only available license is CC0 1.0 but in the JSON "CC BY 4.0" is passed in. Shouldn't it complain that you have to pass CC0 1.0?
created_at: 2025-04-08T19:30:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,82 @@
+{
+  "datasetType": "testDatasetType",
+  "datasetVersion": {
+    "license": {
+      "name": "CC BY 4.0",
+      "uri": "https://creativecommons.org/licenses/by/4.0/"
+    },
+    "metadataBlocks": {
+      "citation": {
+        "fields": [
+          {
+            "value": "pyDataverse",
+            "typeClass": "primitive",
+            "multiple": false,
+            "typeName": "title"
+          },
+          {
+            "value": [
+              {
+                "authorName": {
+                  "value": "Range, Dan",
path: scripts/api/data/dataset-create-new-with-type.json
user: login: sekmiller
body: Needed to make sure I had the right file for testing.
created_at: 2025-04-08T20:01:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +54,12 @@ public class DatasetType implements Serializable {
      */
     @ManyToMany(cascade = {CascadeType.MERGE})
     private List<MetadataBlock> metadataBlocks = new ArrayList<>();
+    
+    /**
+     * The Licenses this dataset type is linked to.
+     */
+    @ManyToMany(cascade = {CascadeType.MERGE})
+    private List<License> licenses = new ArrayList<>();
path: src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
user: login: sekmiller
body: I don't think so, unless we decide we need to pre-fill the values depending on if there are any changes coming out of the "force use of custom terms" discussion
created_at: 2025-04-09T13:43:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
path: src/main/webapp/dataset-license-terms.xhtml
user: login: sekmiller
body: Licenses can also be set on the dataset template.
created_at: 2025-04-09T13:44:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
+                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}" 
+                                                   var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
+                                       <c:if test="#{datasetPage}">
+                                       <f:selectItems value="#{DatasetPage.availableLicenses}" 
                                                    var="license" itemLabel="#{DatasetUtil:getLocalizedLicenseDetails(license, 'NAME')}" itemValue="#{license}"/>
+                                       </c:if>
path: src/main/webapp/dataset-license-terms.xhtml
user: login: sekmiller
body: No, you would have to create the dataset via the api with json that includes a datasetType
created_at: 2025-04-09T13:58:13Z
author_association: CONTRIBUTOR
path: doc/release-notes/10520-datasetType-enable-licenses
user: login: pdurbin
body: Fixed in 8cf2237670.
created_at: 2025-04-16T13:38:11Z
author_association: MEMBER
diff_hunk: @@ -52,9 +52,16 @@
                                </ui:fragment>
                                <ui:fragment rendered="#{!empty editMode}">
                                    <p class="help-block"><h:outputText value="#{bundle['file.dataFilesTab.terms.list.license.edit.description']}" escape="false"/></p>
+
                                    <p:selectOneMenu id="licenses" value="#{termsOfUseAndAccess.license}" style="width: 40%" converter="licenseConverter">
-                                       <f:selectItems value="#{licenseServiceBean.listAllActive()}"
+                                       <c:if test="#{!datasetPage}">
path: src/main/webapp/dataset-license-terms.xhtml
user: login: pdurbin
body: Right, right. Thanks.
created_at: 2025-04-16T15:40:45Z
author_association: MEMBER
diff_hunk: @@ -53,6 +54,12 @@ public class DatasetType implements Serializable {
      */
     @ManyToMany(cascade = {CascadeType.MERGE})
     private List<MetadataBlock> metadataBlocks = new ArrayList<>();
+    
+    /**
+     * The Licenses this dataset type is linked to.
+     */
+    @ManyToMany(cascade = {CascadeType.MERGE})
+    private List<License> licenses = new ArrayList<>();
path: src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java
user: login: pdurbin
body: You're right. I just deployed develop, kept my database, and deployed this branch, and it's fine.
created_at: 2025-04-16T15:47:43Z
author_association: MEMBER
diff_hunk: @@ -3619,7 +3619,7 @@ Add Dataset Type
 
 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.
 
-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.
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: Added in 459f3f9594.
created_at: 2025-04-16T17:53:25Z
author_association: MEMBER
diff_hunk: @@ -3686,6 +3686,34 @@ To update the blocks that are linked, send an array with those blocks.
 
 To remove all links to blocks, send an empty array.
 
+Set Available Licenses for a Dataset Type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+For example, a superuser could create a type called "software" and limit the available licenses to only "CC0 1.0"
+
+This API endpoint is for superusers only.
+
+.. code-block:: bash
+
+  export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+  export SERVER_URL=https://demo.dataverse.org
+  export TYPE=software
+  export JSON='["CC0 1.0"]'
+
+  curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE/licenses" -X PUT -d $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" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software/licenses" -X PUT -d '["CC0 1.0"]'
+
+To update the licenses available, send an array with those licenses.
+
+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.
path: doc/sphinx-guides/source/api/native-api.rst
user: login: pdurbin
body: My take from standup is that we'll go with what's implemented so far and follow up with another pull request if we need different functionality.
created_at: 2025-04-16T17:54:19Z
author_association: MEMBER

PR comments:
user: login: coveralls
created_at: 2025-04-01T17:54:40Z
body: 
[![Coverage Status](https://coveralls.io/builds/73254908/badge)](https://coveralls.io/builds/73254908)

coverage: 23.09% (-0.02%) from 23.111%
when pulling **8cf223767077e0c880489983e390d37a770fc171 on 10520-datasetTypeLicenses**
into **85999938f4e1b196d35671c8f08b7324dda6f98d on develop**.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-01T17:57:00Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-01T19:37:51Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-02T13:44:56Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-03T17:26:50Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-08T20:16:03Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-09T13:52:12Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-09T13:52:55Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-09T14:00:31Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-09T18:54:12Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T13:46:33Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T13:50:59Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T13:53:00Z
body: :package: Pushed preview images as
```
ghcr.io/gdcc/dataverse:10520-datasetTypeLicenses
```
```
ghcr.io/gdcc/configbaker:10520-datasetTypeLicenses
```
:ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container). Use by referencing with full name as printed above, mind the registry name.
<!-- Sticky Pull Request Commentregistry-push -->
author_association: NONE
user: login: ofahimIQSS
created_at: 2025-04-17T14:04:49Z
body: continuous integration and unit tests are failing on this 
author_association: CONTRIBUTOR
user: login: ofahimIQSS
created_at: 2025-04-17T17:41:44Z
body: No issues found with this, merging. 
author_association: CONTRIBUTOR