From 68a90ddb4c11d81ad863e67e2d6a78d06b0b37f5 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 16 Sep 2026 11:40:04 -0400 Subject: [PATCH 01/14] upgrade to Payara 7.2026.9 #12715 --- doc/release-notes/6.12-release-notes.md | 12 ++++++------ .../source/installation/prerequisites.rst | 6 +++--- modules/dataverse-parent/pom.xml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/release-notes/6.12-release-notes.md b/doc/release-notes/6.12-release-notes.md index b50a5be5ebf..a3b6243233e 100644 --- a/doc/release-notes/6.12-release-notes.md +++ b/doc/release-notes/6.12-release-notes.md @@ -41,7 +41,7 @@ These are features that weren't already mentioned under "highlights" above. ## Other Changes -- Payara 7.2026.8 is now the supported and recommended version. See upgrade instructions below and #12498. +- Payara 7.2026.9 is now the supported and recommended version. See upgrade instructions below, #12498 and #12715. - Database indexes were added to the guestbookresponse table to speed up queries. See #12616. - The QA Guide was added as an experiment in #10103 but has been removed because it has proved less useful than imagined. It can still be [viewed](https://guides.dataverse.org/en/6.11/qa/index.html) as it existed as of Dataverse 6.11. See #12582. @@ -164,7 +164,7 @@ If you are running Payara as a non-root user (and you should be!), **remember no Also, we assume that Payara is installed in `/usr/local/payara7`. If not, adjust as needed. -The instructions below describe the upgrade procedure based on moving your existing Payara 7.2026.2 domain directory into the new Payara 7.2026.8 distribution. We recommend this method because it is the easiest way to recreate your current configuration and preserve your data. +The instructions below describe the upgrade procedure based on moving your existing Payara 7.2026.2 domain directory into the new Payara 7.2026.9 distribution. We recommend this method because it is the easiest way to recreate your current configuration and preserve your data. 1. Undeploy Dataverse, if deployed, using the unprivileged service account ("dataverse", by default). @@ -188,12 +188,12 @@ The instructions below describe the upgrade procedure based on moving your exist sudo mv /usr/local/payara7 /usr/local/payara7-2026.2 ``` -1. Download the new Payara version 7.2026.8, and unzip it. +1. Download the new Payara version 7.2026.9, and unzip it. ```shell - curl -L -O https://nexus.payara.fish/repository/payara-community/fish/payara/distributions/payara/7.2026.8/payara-7.2026.8.zip + curl -L -O https://nexus.payara.fish/repository/payara-community/fish/payara/distributions/payara/7.2026.9/payara-7.2026.9.zip - sudo unzip payara-7.2026.8.zip -d /usr/local/ + sudo unzip payara-7.2026.9.zip -d /usr/local/ ``` 1. Set permission for the service account ("dataverse" by default). @@ -223,7 +223,7 @@ The instructions below describe the upgrade procedure based on moving your exist 1. Update configuration files in the moved domain. - The file `glassfish-acc.xml` from Payara 7.2026.2 has been renamed to `payara-acc.xml` in Payara 7.2026.8. Also `default-logging.properties` has been updated in the Payara 7.2026.8. We recommend copying these new files from the Payara 7.2026.8 distribution domain into the domain you copied over from your Payara 7.2026.2 installation. + The file `glassfish-acc.xml` from Payara 7.2026.2 has been renamed to `payara-acc.xml` in Payara 7.2026.9. Also `default-logging.properties` has been updated in the Payara 7.2026.9. We recommend copying these new files from the Payara 7.2026.9 distribution domain into the domain you copied over from your Payara 7.2026.2 installation. ```shell sudo rm /usr/local/payara7/glassfish/domains/domain1/config/glassfish-acc.xml diff --git a/doc/sphinx-guides/source/installation/prerequisites.rst b/doc/sphinx-guides/source/installation/prerequisites.rst index 1d7cfa99081..00203b77d9b 100644 --- a/doc/sphinx-guides/source/installation/prerequisites.rst +++ b/doc/sphinx-guides/source/installation/prerequisites.rst @@ -44,7 +44,7 @@ On RHEL/derivative you can make Java 21 the default with the ``alternatives`` co Payara ------ -Payara 7.2026.8 is recommended. Newer versions might work fine. Regular updates are recommended. +Payara 7.2026.9 is recommended. Newer versions might work fine. Regular updates are recommended. Installing Payara ================= @@ -55,8 +55,8 @@ Installing Payara - Download and install Payara (installed in ``/usr/local/payara7`` in the example commands below):: - # wget https://nexus.payara.fish/repository/payara-community/fish/payara/distributions/payara/7.2026.8/payara-7.2026.8.zip - # unzip payara-7.2026.8.zip + # wget https://nexus.payara.fish/repository/payara-community/fish/payara/distributions/payara/7.2026.9/payara-7.2026.9.zip + # unzip payara-7.2026.9.zip # mv payara7 /usr/local If nexus.payara.fish is ever down for maintenance, Payara distributions are also available from https://repo1.maven.org/maven2/fish/payara/distributions/payara/ diff --git a/modules/dataverse-parent/pom.xml b/modules/dataverse-parent/pom.xml index 2cd63f57da0..49feb52b7bb 100644 --- a/modules/dataverse-parent/pom.xml +++ b/modules/dataverse-parent/pom.xml @@ -149,7 +149,7 @@ -Duser.timezone=${project.timezone} -Dfile.encoding=${project.build.sourceEncoding} -Duser.language=${project.language} -Duser.region=${project.region} - 7.2026.8 + 7.2026.9 42.7.12 9.8.0 16 From ac6f078115b881a7b95c4cf0aa05c2ae5f7f8739 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 16 Sep 2026 12:48:46 -0400 Subject: [PATCH 02/14] @Context to Inject --- .../harvard/iq/dataverse/api/filter/ApiBlockingFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java index 84497b479d4..8ad36ade32d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java @@ -56,10 +56,10 @@ public class ApiBlockingFilter implements ContainerRequestFilter { @Inject private PasswordValidatorServiceBean passwordValidatorService; - @Context + @Inject private ResourceInfo resourceInfo; - @Context + @Inject private HttpServletRequest httpServletRequest; private String policy = null; From a33f3560df81308877e801299af60b8ca6cf2568 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 16 Sep 2026 13:09:14 -0400 Subject: [PATCH 03/14] ResourceProvider to address weld error --- .../harvard/iq/dataverse/api/filter/ApiBlockingFilter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java index 8ad36ade32d..9fe8fe1111a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java @@ -8,14 +8,13 @@ import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean; import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; -import jakarta.json.Json; + import jakarta.json.JsonObject; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Path; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ResourceInfo; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; import java.io.IOException; @@ -57,7 +56,7 @@ public class ApiBlockingFilter implements ContainerRequestFilter { private PasswordValidatorServiceBean passwordValidatorService; @Inject - private ResourceInfo resourceInfo; + private jakarta.inject.Provider resourceProvider; @Inject private HttpServletRequest httpServletRequest; @@ -118,7 +117,7 @@ public void init() { @Override public void filter(ContainerRequestContext requestContext) throws IOException { - + ResourceInfo resourceInfo = resourceProvider.get(); Method method = resourceInfo.getResourceMethod(); Class clazz = resourceInfo.getResourceClass(); From 06e6226c38ab613d559c83ab4626122f0b9c0811 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 16 Sep 2026 13:56:14 -0400 Subject: [PATCH 04/14] Dynamic Feature --- .../api/filter/ApiBlockingFeature.java | 250 ++++++++++++++++++ .../api/filter/ApiBlockingFilter.java | 234 +--------------- 2 files changed, 263 insertions(+), 221 deletions(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFeature.java diff --git a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFeature.java b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFeature.java new file mode 100644 index 00000000000..d5651f01e55 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFeature.java @@ -0,0 +1,250 @@ +package edu.harvard.iq.dataverse.api.filter; + +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.settings.JvmSettings; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean; +import edu.harvard.iq.dataverse.util.json.JsonUtil; +import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.json.JsonObject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; +import org.eclipse.jetty.util.StringUtil; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * A DynamicFeature that registers ApiBlockingFilter for Dataverse API endpoints. + * This approach avoids the need to inject ResourceInfo at request time, which + * is no longer supported via @Context in Jakarta EE 11 / Payara 7. + */ +@Provider +public class ApiBlockingFeature implements DynamicFeature { + + private static final Logger logger = Logger.getLogger(ApiBlockingFeature.class.getName()); + + @Inject + private SettingsServiceBean settingsService; + + @Inject + private PasswordValidatorServiceBean passwordValidatorService; + + @Inject + private HttpServletRequest httpServletRequest; + + // Policies + private static final String DROP = "drop"; + private static final String LOCALHOST_ONLY = "localhost-only"; + private static final String UNBLOCK_KEY = "unblock-key"; + + private static final Map POLICY_ERROR_MESSAGES = new HashMap<>(); + static { + POLICY_ERROR_MESSAGES.put(DROP, "Endpoint blocked. Access denied."); + POLICY_ERROR_MESSAGES.put(LOCALHOST_ONLY, "Endpoint restricted to localhost access only."); + POLICY_ERROR_MESSAGES.put(UNBLOCK_KEY, "Endpoint requires an unblock key for access."); + } + + private String policy = null; + private JsonObject errorJson = null; + private volatile List blockedApiEndpointPatterns = new ArrayList<>(); + private String key; + + // If any of the JvmSettings are not set, revert to checking the db settings on every call + private boolean checkSettings = false; + private String endpointList = null; + + @PostConstruct + public void init() { + // Check JvmSettings first for BlockedApiPolicy + policy = JvmSettings.API_BLOCKED_POLICY.lookupOptional().orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiPolicy, DROP)); + + if(!(DROP.equals(policy) || LOCALHOST_ONLY.equals(policy) || UNBLOCK_KEY.equals(policy))) { + logger.severe("Invalid BlockedApiPolicy setting: " + policy + ". Using policy 'drop'"); + policy = DROP; + } + Optional jvmEndpointList = JvmSettings.API_BLOCKED_ENDPOINTS.lookupOptional(); + if (!jvmEndpointList.isPresent()) { + checkSettings = true; + } + endpointList = jvmEndpointList + .orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, "")); + logger.info("Using policy: " + policy + " to block API endpoints: " + endpointList); + if (!(endpointList.contains("admin") && endpointList.contains("builtin-users"))) { + logger.warning( + "Not blocking admin and builtin-user endpoints is a security issue unless you are blocking them in an external proxy."); + } + if (UNBLOCK_KEY.equals(policy)) { + Optional jvmKey = JvmSettings.API_BLOCKED_KEY.lookupOptional(); + if (!jvmKey.isPresent()) { + checkSettings = true; + } + key = jvmKey.orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiKey)); + if (StringUtil.isBlank(key)) { + logger.severe( + "Using unblock-key policy and no unblock key found in JvmSettings.API_BLOCKED_KEY or SettingsService.BlockedApiKey"); + } else if (passwordValidatorService.validate(key).size() == 0) { + logger.warning("Weak unblock key detected. Please use a stronger key for better security."); + } + } + updateBlockedPoints(endpointList); + if(checkSettings) { + logger.warning("Not all required dataverse.api.blocked.* settings not found. Dataverse use deprecated db settings and check for updates on every API call."); + } + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + Class clazz = resourceInfo.getResourceClass(); + Method method = resourceInfo.getResourceMethod(); + + if (clazz.getName().startsWith("edu.harvard.iq.dataverse")) { + String classPath = ""; + String methodPath = ""; + + if (clazz.isAnnotationPresent(Path.class)) { + classPath = clazz.getAnnotation(Path.class).value(); + } + + if (method.isAnnotationPresent(Path.class)) { + methodPath = method.getAnnotation(Path.class).value(); + } + + String fullPath = (classPath + "/" + methodPath).replaceAll("//", "/"); + context.register(new ApiBlockingFilter(this, fullPath)); + } + } + + public boolean shouldBlock(String fullPath, ContainerRequestContext requestContext) { + if (checkSettings) { + updateSettingsIfChanged(); + } + + boolean isBlockableEndpoint = false; + List currentPatterns = blockedApiEndpointPatterns; + for (Pattern blockedEndpointPattern : currentPatterns) { + if (blockedEndpointPattern.matcher(fullPath).matches()) { + isBlockableEndpoint = true; + break; + } + } + + if (!isBlockableEndpoint) { + return false; + } + + // Blockable endpoint - now check policy + return isBlocked(policy, requestContext); + } + + private synchronized void updateSettingsIfChanged() { + // Backward compatibility, e.g. for setup scripts, dev environments where + // dynamic update from the db settings is expected + String newPolicy = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiPolicy, + JvmSettings.API_BLOCKED_POLICY.lookupOptional().orElse(DROP)); + String newEndpointList = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, + JvmSettings.API_BLOCKED_ENDPOINTS.lookupOptional().orElse("")); + + boolean changed = false; + if (!newPolicy.equals(policy)) { + policy = newPolicy; + changed = true; + } + if (!endpointList.equals(newEndpointList)) { + endpointList = newEndpointList; + changed = true; + } + + if (changed) { + updateBlockedPoints(endpointList); + } + + if (policy.equals(UNBLOCK_KEY)) { + key = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiKey, + JvmSettings.API_BLOCKED_KEY.lookupOptional().orElse("")); + if (StringUtil.isBlank(key)) { + logger.severe( + "Using unblock-key policy and no unblock key found in JvmSettings.API_BLOCKED_KEY or SettingsService.BlockedApiKey"); + } + } + } + + private boolean isBlocked(String policy, ContainerRequestContext requestContext) { + switch (policy) { + case DROP: + return true; + case LOCALHOST_ONLY: + if (httpServletRequest == null) { + logger.warning("Unable to obtain HttpServletRequest"); + return true; + } + IpAddress origin = new DataverseRequest(null, httpServletRequest).getSourceAddress(); + if (!origin.isLocalhost()) { + return true; + } + break; + case UNBLOCK_KEY: + String providedKey = requestContext.getHeaderString(ApiBlockingFilter.UNBLOCK_KEY_HEADER); + if (StringUtil.isBlank(providedKey)) { + providedKey = requestContext.getUriInfo().getQueryParameters().getFirst(ApiBlockingFilter.UNBLOCK_KEY_QUERYPARAM); + } + // Must have a non-blank key defined and the query param must match it + if (StringUtil.isNotBlank(key) && key.equals(providedKey)) { + return false; + } + return true; + } + return false; + } + + private void updateBlockedPoints(String endpointList) { + List newPatterns = new ArrayList<>(); + + String currentErrorMessage = POLICY_ERROR_MESSAGES.getOrDefault(policy, + "Endpoint blocked. Please contact the dataverse administrator."); + + errorJson = JsonUtil.createObjectBuilder().add("status", "error").add("message", currentErrorMessage).build(); + + for (String endpoint : endpointList.split(",")) { + String endpointPrefix = canonicalize(endpoint); + if (!endpointPrefix.isEmpty()) { + logger.log(Level.INFO, "Blocking API endpoint: {0}", endpointPrefix); + newPatterns.add(Pattern.compile(convertPathToRegex(endpointPrefix))); + } + } + blockedApiEndpointPatterns = newPatterns; + } + + private String convertPathToRegex(String path) { + return "^" + path.replaceAll("\\{[^}]+\\}", "[^/]+").replace("/", "\\/") + "(\\/.*)?$"; + } + + private String canonicalize(String in) { + in = in.trim(); + if (in.startsWith("/")) { + in = in.substring(1); + } + if (in.endsWith("/")) { + in = in.substring(0, in.length() - 1); + } + return in; + } + + public JsonObject getErrorJson() { + return errorJson; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java index 9fe8fe1111a..0285fae3410 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/filter/ApiBlockingFilter.java @@ -1,245 +1,37 @@ package edu.harvard.iq.dataverse.api.filter; -import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; -import edu.harvard.iq.dataverse.engine.command.DataverseRequest; -import edu.harvard.iq.dataverse.settings.JvmSettings; -import edu.harvard.iq.dataverse.settings.SettingsServiceBean; -import edu.harvard.iq.dataverse.util.json.JsonUtil; -import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean; -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; - -import jakarta.json.JsonObject; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.Path; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; -import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.Provider; import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Pattern; - -import org.eclipse.jetty.util.StringUtil; -@Provider +/** + * Filter registered by ApiBlockingFeature to block specific Dataverse API endpoints. + */ public class ApiBlockingFilter implements ContainerRequestFilter { private static final Logger logger = Logger.getLogger(ApiBlockingFilter.class.getName()); public static final String UNBLOCK_KEY_QUERYPARAM = "unblock-key"; public static final String UNBLOCK_KEY_HEADER = "X-Dataverse-unblock-key"; - // Policies - private static final String DROP = "drop"; - private static final String LOCALHOST_ONLY = "localhost-only"; - private static final String UNBLOCK_KEY = "unblock-key"; - - private static final Map POLICY_ERROR_MESSAGES = new HashMap<>(); - static { - POLICY_ERROR_MESSAGES.put(DROP, "Endpoint blocked. Access denied."); - POLICY_ERROR_MESSAGES.put(LOCALHOST_ONLY, "Endpoint restricted to localhost access only."); - POLICY_ERROR_MESSAGES.put(UNBLOCK_KEY, "Endpoint requires an unblock key for access."); - } - - @Inject - private SettingsServiceBean settingsService; - - @Inject - private PasswordValidatorServiceBean passwordValidatorService; - - @Inject - private jakarta.inject.Provider resourceProvider; - - @Inject - private HttpServletRequest httpServletRequest; - - private String policy = null; - - private JsonObject errorJson = null; - - private List blockedApiEndpointPatterns = new ArrayList<>(); - - private String key; - // If any of the JvmSettings are not set, revert to checking the db settings on - // every call - private boolean checkSettings = false; - - private String endpointList = null; - - @PostConstruct - public void init() { - // Check JvmSettings first for BlockedApiPolicy - policy = JvmSettings.API_BLOCKED_POLICY.lookupOptional().orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiPolicy, DROP)); - - if(!(DROP.equals(policy) || LOCALHOST_ONLY.equals(policy) || UNBLOCK_KEY.equals(policy))) { - logger.severe("Invalid BlockedApiPolicy setting: " + policy + ". Using policy 'drop'"); - policy = DROP; - } - Optional jvmEndpointList = JvmSettings.API_BLOCKED_ENDPOINTS.lookupOptional(); - if (!jvmEndpointList.isPresent()) { - checkSettings = true; - } - endpointList = jvmEndpointList - .orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, "")); - logger.info("Using policy: " + policy + " to block API endpoints: " + endpointList); - if (!(endpointList.contains("admin") && endpointList.contains("builtin-users"))) { - logger.warning( - "Not blocking admin and builtin-user endpoints is a security issue unless you are blocking them in an external proxy."); - } - if (UNBLOCK_KEY.equals(policy)) { - Optional jvmKey = JvmSettings.API_BLOCKED_KEY.lookupOptional(); - if (!jvmKey.isPresent()) { - checkSettings = true; - } - key = jvmKey.orElse(settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiKey)); - if (StringUtil.isBlank(key)) { - logger.severe( - "Using unblock-key policy and no unblock key found in JvmSettings.API_BLOCKED_KEY or SettingsService.BlockedApiKey"); - } else if (passwordValidatorService.validate(key).size() == 0) { - logger.warning("Weak unblock key detected. Please use a stronger key for better security."); - } - } - updateBlockedPoints(endpointList); - if(checkSettings) { - logger.warning("Not all required dataverse.api.blocked.* settings not found. Dataverse use deprecated db settings and check for updates on every API call."); - } + private final ApiBlockingFeature feature; + private final String fullPath; + public ApiBlockingFilter(ApiBlockingFeature feature, String fullPath) { + this.feature = feature; + this.fullPath = fullPath; } @Override public void filter(ContainerRequestContext requestContext) throws IOException { - ResourceInfo resourceInfo = resourceProvider.get(); - Method method = resourceInfo.getResourceMethod(); - Class clazz = resourceInfo.getResourceClass(); - - String classPath = ""; - String methodPath = ""; - - if (clazz.isAnnotationPresent(Path.class)) { - classPath = clazz.getAnnotation(Path.class).value(); - } - - if (method.isAnnotationPresent(Path.class)) { - methodPath = method.getAnnotation(Path.class).value(); - } - - if (checkSettings) { - // Backward compatibility, e.g. for setup scripts, dev environments where - // dynamic update from the db settings is expected - policy = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiPolicy, - JvmSettings.API_BLOCKED_POLICY.lookupOptional().orElse(DROP)); - String newEndpointList = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, - JvmSettings.API_BLOCKED_ENDPOINTS.lookupOptional().orElse("")); - if (!endpointList.equals(newEndpointList)) { - endpointList = newEndpointList; - updateBlockedPoints(endpointList); - } - if (policy.equals(UNBLOCK_KEY)) { - key = settingsService.getValueForKey(SettingsServiceBean.Key.BlockedApiKey, - JvmSettings.API_BLOCKED_KEY.lookupOptional().orElse("")); - if (StringUtil.isBlank(key)) { - logger.severe( - "Using unblock-key policy and no unblock key found in JvmSettings.API_BLOCKED_KEY or SettingsService.BlockedApiKey"); - } - } - } - String fullPath = (classPath + "/" + methodPath).replaceAll("//", "/"); - logger.fine("Full path is " + fullPath); - - boolean isBlockableEndpoint = false; - for (Pattern blockedEndpointPattern : blockedApiEndpointPatterns) { - if (blockedEndpointPattern.matcher(fullPath).matches()) { - isBlockableEndpoint = true; - break; - } - } - if (!isBlockableEndpoint) { - return; - } - // Blocakble endpoint - now check policy - if (isBlocked(policy, requestContext)) { + if (feature.shouldBlock(fullPath, requestContext)) { logger.fine("Blocked " + fullPath); - requestContext.abortWith(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(errorJson) - .type(jakarta.ws.rs.core.MediaType.APPLICATION_JSON).build()); - return; - } - } - - private boolean isBlocked(String policy, ContainerRequestContext requestContext) { - switch (policy) { - case DROP: - return true; - case LOCALHOST_ONLY: - if (httpServletRequest == null) { - logger.warning("Unable to obtain HttpServletRequest from ContainerRequestContext"); - // Handle the case where HttpServletRequest is not available - return true; - } - IpAddress origin = new DataverseRequest(null, httpServletRequest).getSourceAddress(); - if (!origin.isLocalhost()) { - return true; - } - break; - case UNBLOCK_KEY: - String providedKey = requestContext.getHeaderString(UNBLOCK_KEY_HEADER); - if (StringUtil.isBlank(providedKey)) { - providedKey = requestContext.getUriInfo().getQueryParameters().getFirst(UNBLOCK_KEY_QUERYPARAM); - } - // Must have a non-blank key defined and the query param must match it - if (StringUtil.isNotBlank(key) && key.equals(providedKey)) { - return false; - } - // Otherwise we have a blocked endpoint and the key doesn't work (not set or - // doesn't match what's sent) - return true; - } - return false; - } - - private void updateBlockedPoints(String endpointList) { - blockedApiEndpointPatterns.clear(); - - String currentErrorMessage = POLICY_ERROR_MESSAGES.getOrDefault(policy, - "Endpoint blocked. Please contact the dataverse administrator."); - - errorJson = JsonUtil.createObjectBuilder().add("status", "error").add("message", currentErrorMessage).build(); - - for (String endpoint : endpointList.split(",")) { - String endpointPrefix = canonicalize(endpoint); - if (!endpointPrefix.isEmpty()) { - logger.log(Level.INFO, "Blocking API endpoint: {0}", endpointPrefix); - blockedApiEndpointPatterns.add(Pattern.compile(convertPathToRegex(endpointPrefix))); - } - } - } - - private String convertPathToRegex(String path) { - return "^" + path.replaceAll("\\{[^}]+\\}", "[^/]+").replace("/", "\\/") + "(\\/.*)?$"; - } - - /** - * Creates a canonical representation of {@code in}: trimmed spaces and slashes - * - * @param in the raw string - * @return {@code in} with no trailing and leading spaces and slashes. - */ - private String canonicalize(String in) { - in = in.trim(); - if (in.startsWith("/")) { - in = in.substring(1); - } - if (in.endsWith("/")) { - in = in.substring(0, in.length() - 1); + requestContext.abortWith(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity(feature.getErrorJson()) + .type(jakarta.ws.rs.core.MediaType.APPLICATION_JSON) + .build()); } - return in; } } \ No newline at end of file From 701955debcc0a3476513ad17fb465781a2e6ac43 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 16 Sep 2026 16:14:54 -0400 Subject: [PATCH 05/14] dynamically load dialog --- src/main/webapp/contactFormFragment.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/contactFormFragment.xhtml b/src/main/webapp/contactFormFragment.xhtml index 470a137e6cf..a558eb61ad8 100644 --- a/src/main/webapp/contactFormFragment.xhtml +++ b/src/main/webapp/contactFormFragment.xhtml @@ -5,7 +5,7 @@ xmlns:p="http://primefaces.org/ui" xmlns:jsf="http://xmlns.jcp.org/jsf"> - + From 347312a2736a9789023da93c33603277b3c43359 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 17 Sep 2026 09:34:05 -0400 Subject: [PATCH 06/14] more dynamic loading and scope limiting --- src/main/webapp/dataset.xhtml | 2 +- src/main/webapp/dataverse_header.xhtml | 4 ++-- src/main/webapp/dataverseuser.xhtml | 6 +++--- src/main/webapp/explicitGroup-new-dialog.xhtml | 2 +- src/main/webapp/loginpage.xhtml | 4 ++-- src/main/webapp/manage-groups.xhtml | 4 ++-- src/main/webapp/oauth2/firstLogin.xhtml | 2 +- src/main/webapp/themeAndWidgetsFragment.xhtml | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml index f9565d27691..067a62b1c42 100644 --- a/src/main/webapp/dataset.xhtml +++ b/src/main/webapp/dataset.xhtml @@ -1454,7 +1454,7 @@ - +
  diff --git a/src/main/webapp/dataverse_header.xhtml b/src/main/webapp/dataverse_header.xhtml index 7cc47435170..f206a499a9e 100644 --- a/src/main/webapp/dataverse_header.xhtml +++ b/src/main/webapp/dataverse_header.xhtml @@ -87,7 +87,7 @@
  • - + @@ -203,7 +203,7 @@
    - +
    diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index a46433e3962..acf11f1a724 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -95,7 +95,7 @@
  • + action="#{DataverseUserPage.save}" onclick="PF('muteCollapse').collapse('hide')" update="@form" process="@form"/> @@ -734,7 +734,7 @@ - +

    @@ -873,7 +873,7 @@

    + action="#{DataverseUserPage.save}" update="@form,:messagePanel,:userDisplayInfoTitle" process="@form"/> diff --git a/src/main/webapp/explicitGroup-new-dialog.xhtml b/src/main/webapp/explicitGroup-new-dialog.xhtml index 96c78969ca1..ea5fbfce68f 100644 --- a/src/main/webapp/explicitGroup-new-dialog.xhtml +++ b/src/main/webapp/explicitGroup-new-dialog.xhtml @@ -4,7 +4,7 @@ xmlns:p="http://primefaces.org/ui" xmlns:iqbs="http://xmlns.jcp.org/jsf/composite/iqbs"> - +
    diff --git a/src/main/webapp/loginpage.xhtml b/src/main/webapp/loginpage.xhtml index ffb2ce0f935..2acd03634af 100644 --- a/src/main/webapp/loginpage.xhtml +++ b/src/main/webapp/loginpage.xhtml @@ -93,7 +93,7 @@
    - +
    @@ -218,7 +218,7 @@

    #{bundle['auth.providers.title']}

    - + diff --git a/src/main/webapp/manage-groups.xhtml b/src/main/webapp/manage-groups.xhtml index ed63540cf96..b1e9cf5843e 100644 --- a/src/main/webapp/manage-groups.xhtml +++ b/src/main/webapp/manage-groups.xhtml @@ -87,7 +87,7 @@
    - +

    #{bundle['dataverse.manageGroups.tab.action.btn.delete.dialog.tip']}

    @@ -96,7 +96,7 @@
    - +
    diff --git a/src/main/webapp/oauth2/firstLogin.xhtml b/src/main/webapp/oauth2/firstLogin.xhtml index 07b1631d6cb..75cc436083e 100644 --- a/src/main/webapp/oauth2/firstLogin.xhtml +++ b/src/main/webapp/oauth2/firstLogin.xhtml @@ -27,7 +27,7 @@
    - +

    #{OAuth2FirstLoginPage.createFromWhereTip} diff --git a/src/main/webapp/themeAndWidgetsFragment.xhtml b/src/main/webapp/themeAndWidgetsFragment.xhtml index 42fd941b7da..dfe56800c52 100644 --- a/src/main/webapp/themeAndWidgetsFragment.xhtml +++ b/src/main/webapp/themeAndWidgetsFragment.xhtml @@ -6,7 +6,7 @@ xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions"> - + From 649b69b61d0ce254167819c39c549d5f8731e628 Mon Sep 17 00:00:00 2001 From: qqmyers Date: Thu, 17 Sep 2026 13:58:08 -0400 Subject: [PATCH 07/14] trigger error for null req. email --- .../authorization/providers/builtin/DataverseUserPage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 3de54cd6983..16f3166551f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -69,6 +69,7 @@ import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP; import java.io.IOException; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.util.StringUtil; import org.json.JSONObject; import org.primefaces.event.TabChangeEvent; @@ -253,7 +254,7 @@ public void validateUserName(FacesContext context, UIComponent toValidate, Objec public void validateUserEmail(FacesContext context, UIComponent toValidate, Object value) { String userEmail = (String) value; - boolean emailValid = EMailValidator.isEmailValid(userEmail); + boolean emailValid = StringUtil.isNotBlank(userEmail) && EMailValidator.isEmailValid(userEmail); if (!emailValid) { ((UIInput) toValidate).setValid(false); FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, BundleUtil.getStringFromBundle("oauth2.newAccount.emailInvalid"), null); From 154a9220d597cd8a4c4aced2a610a9c4cbce90df Mon Sep 17 00:00:00 2001 From: qqmyers Date: Thu, 17 Sep 2026 14:26:42 -0400 Subject: [PATCH 08/14] clear message panel (e.g. validation errs) on cancel --- src/main/webapp/dataverseuser.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index acf11f1a724..4eee768ef81 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -875,7 +875,7 @@ value="#{DataverseUserPage.editMode == 'CREATE' ? bundle['user.createBtn']:bundle.saveChanges}" action="#{DataverseUserPage.save}" update="@form,:messagePanel,:userDisplayInfoTitle" process="@form"/> + action="#{DataverseUserPage.cancel}" process="@this" update="@form,:messagePanel">

    From 641602e58569a73a93c34bbba3fade0ec7c08fdb Mon Sep 17 00:00:00 2001 From: qqmyers Date: Thu, 17 Sep 2026 15:20:59 -0400 Subject: [PATCH 09/14] release note --- .../21715-Payara-7.2026.9-update.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 doc/release-notes/21715-Payara-7.2026.9-update.md diff --git a/doc/release-notes/21715-Payara-7.2026.9-update.md b/doc/release-notes/21715-Payara-7.2026.9-update.md new file mode 100644 index 00000000000..9e0ec1cc8e1 --- /dev/null +++ b/doc/release-notes/21715-Payara-7.2026.9-update.md @@ -0,0 +1,89 @@ +This PR updates Dataverse to be compatible with Payara version 7.2026.9 which includes breaking changes for our code. + +## Upgrade Instructions + +Upgrading requires a maintenance window and downtime. Please plan accordingly, create backups of your database, etc. + +Note: These instructions assume that you are upgrading from the immediate previous version. That is to say, you've already upgraded through all the 6.x releases and are now running Dataverse 6.12. See [tags on GitHub](https://github.com/IQSS/dataverse/tags) for a list of versions. If you are running an earlier version, the only supported way to upgrade is to progress through the upgrades to all the releases in between before attempting the upgrade to this version. + +If you are running Payara as a non-root user (and you should be!), **remember not to execute the commands below as root**. By default, Payara runs as the `dataverse` user. In the commands below, we use sudo to run the commands as a non-root user. + +Also, we assume that Payara is installed in `/usr/local/payara7`. If not, adjust as needed. + +The instructions below describe the upgrade procedure based on moving your existing Payara 7.2026.8 domain directory into the new Payara 7.2026.9 distribution. We recommend this method because it is the easiest way to recreate your current configuration and preserve your data. + +1. Undeploy Dataverse, if deployed, using the unprivileged service account ("dataverse", by default). + + The new version of Payara is not compatible with previous versions of Dataverse, so you should undeploy the running Dataverse 6.12 war file first. + + ```shell + sudo -u dataverse /usr/local/payara7/bin/asadmin list-applications + + sudo -u dataverse /usr/local/payara7/bin/asadmin undeploy dataverse-6.12 + ``` + +1. Stop Payara. + + ```shell + sudo systemctl stop payara + ``` + +1. Move the current Payara 7.2026.8 directory out of the way. + + ```shell + sudo mv /usr/local/payara7 /usr/local/payara7-2026.8 + ``` + +1. Download the new Payara version 7.2026.9, and unzip it. + + ```shell + curl -L -O https://nexus.payara.fish/repository/payara-community/fish/payara/distributions/payara/7.2026.9/payara-7.2026.9.zip + + sudo unzip payara-7.2026.9.zip -d /usr/local/ + ``` +1. Set permission for the service account ("dataverse" by default). + + ```shell + sudo chown -R root:root /usr/local/payara7 + ``` + + Is this needed? We've said it previously, but I've forgotten why and I haven't found it to be needed. + ```shell + sudo chown dataverse /usr/local/payara7/glassfish/lib + ``` + + This shouldn't be needed, but was in earlier instructions + ```shell + sudo chown -R dataverse:dataverse /usr/local/payara7/glassfish/domains/domain1 + ``` + +1. Replace the brand new `payara7/glassfish/domains/domain1` with your old, preserved domain1. + + ```shell + sudo mv /usr/local/payara7/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/domain1_DIST + + sudo cp -ar /usr/local/payara7-2026.8/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/ + ``` + +1. Remove the cache directories. + + ```shell + sudo rm -rf /usr/local/payara7/glassfish/domains/domain1/generated/ + + sudo rm -rf /usr/local/payara7/glassfish/domains/domain1/osgi-cache/ + ``` + +1. Start Payara. + + ```shell + sudo systemctl start payara + ``` + +1. Deploy the Dataverse 6.12.1 war file. + + ```shell + wget https://github.com/IQSS/dataverse/releases/download/v6.12/dataverse-6.12.1.war + + sudo -u dataverse /usr/local/payara7/bin/asadmin deploy dataverse-6.12.1.war + ``` + From e394a32449c0554a2cd969596f842c3d43c7b22c Mon Sep 17 00:00:00 2001 From: qqmyers Date: Thu, 17 Sep 2026 17:06:27 -0400 Subject: [PATCH 10/14] mv instead of cp --- doc/release-notes/21715-Payara-7.2026.9-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes/21715-Payara-7.2026.9-update.md b/doc/release-notes/21715-Payara-7.2026.9-update.md index 9e0ec1cc8e1..c04dfe24d6b 100644 --- a/doc/release-notes/21715-Payara-7.2026.9-update.md +++ b/doc/release-notes/21715-Payara-7.2026.9-update.md @@ -62,7 +62,7 @@ The instructions below describe the upgrade procedure based on moving your exist ```shell sudo mv /usr/local/payara7/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/domain1_DIST - sudo cp -ar /usr/local/payara7-2026.8/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/ + sudo mv /usr/local/payara7-2026.8/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/ ``` 1. Remove the cache directories. From 289cb6d9aaac2a6a81654bb90c4c294738301dd6 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Sep 2026 12:16:43 -0400 Subject: [PATCH 11/14] replace @Context in LDNInbox per review --- src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java b/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java index 84cdf2964f4..0260b6e0180 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java @@ -20,17 +20,16 @@ import java.util.logging.Logger; import jakarta.ejb.EJB; +import jakarta.inject.Inject; import jakarta.json.JsonObject; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.ServiceUnavailableException; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.ForbiddenException; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; @@ -58,7 +57,7 @@ public class LDNInbox extends AbstractApiBean { @EJB RoleAssigneeServiceBean roleAssigneeService; - @Context + @Inject protected HttpServletRequest httpRequest; public static final JsonLDNamespace activityStreams = JsonLDNamespace.defineNamespace("as", From db98c5fcdbe765310e68e8cfdcf4aeba9d13777e Mon Sep 17 00:00:00 2001 From: qqmyers Date: Wed, 23 Sep 2026 16:00:50 -0400 Subject: [PATCH 12/14] Apply batched suggestions from code review Co-authored-by: Philip Durbin --- doc/release-notes/21715-Payara-7.2026.9-update.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/release-notes/21715-Payara-7.2026.9-update.md b/doc/release-notes/21715-Payara-7.2026.9-update.md index c04dfe24d6b..db93008b72e 100644 --- a/doc/release-notes/21715-Payara-7.2026.9-update.md +++ b/doc/release-notes/21715-Payara-7.2026.9-update.md @@ -47,12 +47,6 @@ The instructions below describe the upgrade procedure based on moving your exist sudo chown -R root:root /usr/local/payara7 ``` - Is this needed? We've said it previously, but I've forgotten why and I haven't found it to be needed. - ```shell - sudo chown dataverse /usr/local/payara7/glassfish/lib - ``` - - This shouldn't be needed, but was in earlier instructions ```shell sudo chown -R dataverse:dataverse /usr/local/payara7/glassfish/domains/domain1 ``` From 491e19f317e0cf20b22609a8d4701ccbfe5390f2 Mon Sep 17 00:00:00 2001 From: qqmyers Date: Wed, 23 Sep 2026 16:01:50 -0400 Subject: [PATCH 13/14] Apply batched suggestions from code review2 Co-authored-by: landreev --- doc/release-notes/21715-Payara-7.2026.9-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes/21715-Payara-7.2026.9-update.md b/doc/release-notes/21715-Payara-7.2026.9-update.md index db93008b72e..1320a7ff14c 100644 --- a/doc/release-notes/21715-Payara-7.2026.9-update.md +++ b/doc/release-notes/21715-Payara-7.2026.9-update.md @@ -28,7 +28,7 @@ The instructions below describe the upgrade procedure based on moving your exist sudo systemctl stop payara ``` -1. Move the current Payara 7.2026.8 directory out of the way. +1. Move the current Payara directory out of the way. Version 7.2026.8 is used in the examples below. Adjust accordingly if you are upgrading from a different version. ```shell sudo mv /usr/local/payara7 /usr/local/payara7-2026.8 From 8c0bb658449e5c8b5313882065769b52d0f7d102 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 23 Sep 2026 16:09:38 -0400 Subject: [PATCH 14/14] chown domain after move, adjust text for the set ownership steps --- doc/release-notes/21715-Payara-7.2026.9-update.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/release-notes/21715-Payara-7.2026.9-update.md b/doc/release-notes/21715-Payara-7.2026.9-update.md index 1320a7ff14c..b0186931137 100644 --- a/doc/release-notes/21715-Payara-7.2026.9-update.md +++ b/doc/release-notes/21715-Payara-7.2026.9-update.md @@ -41,15 +41,11 @@ The instructions below describe the upgrade procedure based on moving your exist sudo unzip payara-7.2026.9.zip -d /usr/local/ ``` -1. Set permission for the service account ("dataverse" by default). +1. Set ownership of the payara distribution to root, making it read-only for the service account ("dataverse" by default). ```shell sudo chown -R root:root /usr/local/payara7 ``` - - ```shell - sudo chown -R dataverse:dataverse /usr/local/payara7/glassfish/domains/domain1 - ``` 1. Replace the brand new `payara7/glassfish/domains/domain1` with your old, preserved domain1. @@ -59,6 +55,12 @@ The instructions below describe the upgrade procedure based on moving your exist sudo mv /usr/local/payara7-2026.8/glassfish/domains/domain1 /usr/local/payara7/glassfish/domains/ ``` +1. Set ownership of the domain for the service account ("dataverse" by default). Ownership should have been preserved during the move above, but this step assures that the domain is writable by the service account. + + ```shell + sudo chown -R dataverse:dataverse /usr/local/payara7/glassfish/domains/domain1 + ``` + 1. Remove the cache directories. ```shell