diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..9c7a10e1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly diff --git a/Dockerfile.software b/Dockerfile.software index fb63fd73..20453674 100644 --- a/Dockerfile.software +++ b/Dockerfile.software @@ -52,7 +52,7 @@ RUN wget https://github.com/kermitt2/Pub2TEI/archive/refs/heads/master.zip && \ # build runtime image # ------------------- -FROM lfoppiano/grobid:0.9.0-full AS runtime +FROM lfoppiano/grobid:0.9.1-full AS runtime # setting locale is likely useless but to be sure ENV LANG C.UTF-8 diff --git a/build.gradle b/build.gradle index ccad7e9a..2137e5db 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,6 @@ plugins { id 'com.gradleup.shadow' version '8.3.10' id 'distribution' id 'application' - id "org.jetbrains.kotlin.jvm" version "2.0.21" } apply plugin: 'jacoco' @@ -39,13 +38,11 @@ apply plugin: 'com.github.kt3k.coveralls' def getGitRevision() { def gitRevision = "unknown" try { - println("Checking git under ${rootProject.rootDir}") def result = providers.exec { workingDir = rootProject.rootDir commandLine 'git', 'describe', '--tags', '--always', '--first-parent' } gitRevision = result.standardOutput.asText.get().trim() - println "Current Git revision: ${gitRevision}" } catch (Exception e) { println "Could not get Git revision: ${e}" } @@ -54,7 +51,6 @@ def getGitRevision() { project.ext.gitRevision = getGitRevision() -// Create a task to expose the revision for other tasks tasks.register('collectGitRevision') { project.ext.gitRevision = getGitRevision() doLast { @@ -118,8 +114,8 @@ dependencies { //compile group: 'org.dkpro.statistics', name: 'dkpro-statistics-agreement', version: '2.2.0-SNAPSHOT' implementation "me.tongfei:progressbar:0.9.0" - //Logging - implementation 'org.slf4j:slf4j-api:1.7.36' + //Logging (slf4j 2.0.x / logback 1.5.x move together with Dropwizard 5) + implementation 'org.slf4j:slf4j-api:2.0.17' //Apache commons implementation group: 'commons-pool', name: 'commons-pool', version: '1.6' @@ -130,16 +126,22 @@ dependencies { implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.5.0' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.14.1' - //Dropwizard - implementation 'ru.vyarus:dropwizard-guicey:7.3.1' - - implementation 'io.dropwizard:dropwizard-bom:4.0.17' - implementation 'io.dropwizard:dropwizard-core:4.0.17' - implementation 'io.dropwizard:dropwizard-assets:4.0.17' - implementation 'io.dropwizard:dropwizard-testing:4.0.17' - implementation 'io.dropwizard:dropwizard-forms:4.0.17' - implementation 'io.dropwizard:dropwizard-client:4.0.17' - implementation 'io.dropwizard:dropwizard-auth:4.0.17' + //Dropwizard 5.x (Jakarta EE 10 / Jetty 12) to match Grobid 0.9.1. guicey 8.x is the + // Dropwizard-5-compatible line. Jetty 12 removed the org.eclipse.jetty.servlets + // CrossOriginFilter/QoSFilter; the service now uses CrossOriginHandler/QoSHandler. + implementation 'ru.vyarus:dropwizard-guicey:8.0.2' + + // Dropwizard 5.0.1 ships Jetty 12.1.5; force the Jetty BOMs to 12.1.9 (CVE-2026-2332). + implementation platform('org.eclipse.jetty:jetty-bom:12.1.9') + implementation platform('org.eclipse.jetty.ee10:jetty-ee10-bom:12.1.9') + + implementation 'io.dropwizard:dropwizard-bom:5.0.1' + implementation 'io.dropwizard:dropwizard-core:5.0.1' + implementation 'io.dropwizard:dropwizard-assets:5.0.1' + implementation 'io.dropwizard:dropwizard-testing:5.0.1' + implementation 'io.dropwizard:dropwizard-forms:5.0.1' + implementation 'io.dropwizard:dropwizard-client:5.0.1' + implementation 'io.dropwizard:dropwizard-auth:5.0.1' implementation 'io.dropwizard.metrics:metrics-core:4.2.38' implementation 'io.dropwizard.metrics:metrics-servlets:4.2.38' @@ -170,13 +172,13 @@ dependencies { implementation group: 'org.semanticweb.yars', name: 'nxparser-parsers', version: '2.3.3' //Grobid - implementation(group: 'org.grobid', name: 'grobid-core', version: '0.9.0') { + implementation(group: 'org.grobid', name: 'grobid-core', version: '0.9.1') { exclude group: 'org.slf4j', module: "slf4j-log4j12" } - api(group: 'org.grobid', name: 'grobid-service', version: '0.9.0') { + api(group: 'org.grobid', name: 'grobid-service', version: '0.9.1') { exclude group: 'org.slf4j', module: "slf4j-log4j12" } - implementation(group: 'org.grobid', name: 'grobid-trainer', version: '0.9.0') { + implementation(group: 'org.grobid', name: 'grobid-trainer', version: '0.9.1') { exclude group: 'org.slf4j', module: "slf4j-log4j12" } @@ -197,11 +199,9 @@ dependencies { testImplementation 'org.easymock:easymock:5.2.0' testImplementation 'org.hamcrest:hamcrest-all:1.3' testImplementation 'org.hamcrest:hamcrest-library:2.2' - testImplementation 'org.powermock:powermock-module-junit4:2.0.9' - testImplementation 'org.powermock:powermock-api-easymock:2.0.9' - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation "io.mockk:mockk:1.13.9" - + // JUnit 4 API for compilation (was previously provided transitively by + // powermock-module-junit4); JUnit 4 tests are executed by junit-vintage-engine. + testImplementation 'junit:junit:4.13.2' } configurations.all { @@ -253,7 +253,7 @@ test { jvmArgs "--add-opens", "java.base/java.util.stream=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED", "--add-opens", "java.xml/jdk.xml.internal=ALL-UNNAMED", - // PowerMock compatibility with Java 21 — matches parent grobid + // Reflective access needed by the test mocking framework (EasyMock) on Java 21 "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.util=ALL-UNNAMED" } @@ -325,10 +325,6 @@ jar { enabled = true } -artifacts { - archives shadowJar -} - distZip.enabled = true distTar.enabled = false shadowDistZip.enabled = false diff --git a/localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.jar b/localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.jar similarity index 93% rename from localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.jar rename to localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.jar index 0ab27c5b..b837da3b 100644 Binary files a/localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.jar and b/localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.jar differ diff --git a/localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.pom b/localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.pom similarity index 88% rename from localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.pom rename to localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.pom index 63774334..2e542cad 100644 --- a/localLibs/org/grobid/grobid-core/0.9.0/grobid-core-0.9.0.pom +++ b/localLibs/org/grobid/grobid-core/0.9.1/grobid-core-0.9.1.pom @@ -8,7 +8,13 @@ 4.0.0 org.grobid grobid-core - 0.9.0 + 0.9.1 + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + xerces @@ -16,12 +22,6 @@ 2.12.2 compile - - net.arnx - jsonic - 1.3.10 - compile - net.sf.saxon Saxon-HE @@ -43,7 +43,7 @@ org.jetbrains.kotlin kotlin-stdlib - 2.0.21 + 2.2.20 compile @@ -88,16 +88,10 @@ 1.15.0 runtime - - commons-dbutils - commons-dbutils - 1.8.1 - runtime - com.google.guava guava - 33.5.0-jre + 33.6.0-jre runtime @@ -121,25 +115,19 @@ com.fasterxml.jackson.core jackson-core - 2.21.1 + 2.21.4 runtime com.fasterxml.jackson.core jackson-databind - 2.21.1 - runtime - - - com.fasterxml.jackson.module - jackson-module-afterburner - 2.21.1 + 2.21.4 runtime com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.21.1 + 2.21.4 runtime @@ -151,19 +139,19 @@ org.slf4j slf4j-api - 1.7.36 + 2.0.17 runtime ch.qos.logback logback-classic - 1.2.13 + 1.5.26 runtime org.apache.pdfbox pdfbox - 2.0.35 + 2.0.36 runtime @@ -181,7 +169,7 @@ org.apache.opennlp opennlp-tools - 1.9.4 + 2.5.9 runtime @@ -190,5 +178,11 @@ 9.4.12.1 runtime + + net.arnx + jsonic + 1.3.10 + runtime + diff --git a/localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.jar b/localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.jar similarity index 95% rename from localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.jar rename to localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.jar index e699bdbf..550a553c 100644 Binary files a/localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.jar and b/localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.jar differ diff --git a/localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.pom b/localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.pom similarity index 65% rename from localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.pom rename to localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.pom index bcc4748b..016cfe36 100644 --- a/localLibs/org/grobid/grobid-service/0.9.0/grobid-service-0.9.0.pom +++ b/localLibs/org/grobid/grobid-service/0.9.1/grobid-service-0.9.1.pom @@ -1,213 +1,247 @@ + + + + + 4.0.0 org.grobid grobid-service - 0.9.0 + 0.9.1 + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + org.eclipse.jetty + jetty-bom + 12.1.9 + pom + import + + + org.eclipse.jetty.ee10 + jetty-ee10-bom + 12.1.9 + pom + import + + + - org.grobid - grobid-core - 0.9.0 + org.jetbrains.kotlin + kotlin-stdlib + 2.2.20 compile - org.grobid - grobid-trainer - 0.9.0 - compile + com.cybozu.labs + langdetect + 1.1-20120112 + runtime - ru.vyarus - dropwizard-guicey - 7.3.1 - compile + com.rockymadden.stringmetric + stringmetric-core_2.11 + 0.27.4 + runtime - io.dropwizard - dropwizard-bom - 4.0.17 - compile - pom + commons-pool + commons-pool + 1.6 + runtime - io.dropwizard - dropwizard-core - 4.0.17 - compile + commons-io + commons-io + 2.21.0 + runtime - io.dropwizard - dropwizard-assets - 4.0.17 - compile + org.apache.commons + commons-lang3 + 3.20.0 + runtime - io.dropwizard - dropwizard-testing - 4.0.17 - compile + org.apache.commons + commons-collections4 + 4.5.0 + runtime - io.dropwizard.modules - dropwizard-testing-junit4 - 4.0.16 - compile + org.apache.commons + commons-text + 1.15.0 + runtime - io.dropwizard - dropwizard-forms - 4.0.17 - compile + com.google.guava + guava + 33.6.0-jre + runtime - io.dropwizard - dropwizard-client - 4.0.17 - compile + org.apache.httpcomponents + httpclient + 4.5.14 + runtime - io.dropwizard - dropwizard-auth - 4.0.17 - compile + black.ninia + jep + 4.3.1 + runtime - io.dropwizard.metrics - metrics-core - 4.2.38 - compile + com.github.pemistahl + lingua + 1.2.2 + runtime - io.dropwizard.metrics - metrics-servlets - 4.2.38 - compile + com.fasterxml.jackson.core + jackson-core + 2.21.4 + runtime - io.dropwizard - dropwizard-json-logging - 4.0.17 - compile + com.fasterxml.jackson.core + jackson-databind + 2.21.4 + runtime - org.apache.pdfbox - pdfbox - 2.0.35 - compile + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.21.4 + runtime - javax.activation - activation - 1.1.1 - compile + org.grobid + grobid-core + 0.9.1 + runtime - io.prometheus - simpleclient_dropwizard - 0.16.0 - compile + org.grobid + grobid-trainer + 0.9.1 + runtime - io.prometheus - simpleclient_servlet - 0.16.0 - compile + ru.vyarus + dropwizard-guicey + 8.0.2 + runtime - com.cybozu.labs - langdetect - 1.1-20120112 + io.dropwizard + dropwizard-bom + 5.0.1 runtime - com.rockymadden.stringmetric - stringmetric-core_2.11 - 0.27.4 + io.dropwizard + dropwizard-core + 5.0.1 runtime - commons-pool - commons-pool - 1.6 + io.dropwizard + dropwizard-assets + 5.0.1 runtime - commons-io - commons-io - 2.21.0 + io.dropwizard + dropwizard-testing + 5.0.1 runtime - org.apache.commons - commons-lang3 - 3.20.0 + io.dropwizard.modules + dropwizard-testing-junit4 + 5.0.0 runtime - org.apache.commons - commons-collections4 - 4.5.0 + io.dropwizard + dropwizard-forms + 5.0.1 runtime - org.apache.commons - commons-text - 1.15.0 + io.dropwizard + dropwizard-client + 5.0.1 runtime - commons-dbutils - commons-dbutils - 1.8.1 + io.dropwizard + dropwizard-auth + 5.0.1 runtime - com.google.guava - guava - 33.5.0-jre + io.dropwizard.metrics + metrics-core + 4.2.38 runtime - org.apache.httpcomponents - httpclient - 4.5.14 + io.dropwizard.metrics + metrics-servlets + 4.2.38 runtime - black.ninia - jep - 4.3.1 + io.dropwizard + dropwizard-json-logging + 5.0.1 runtime - com.github.pemistahl - lingua - 1.2.2 + org.apache.pdfbox + pdfbox + 2.0.36 runtime - com.fasterxml.jackson.core - jackson-core - 2.21.1 + io.prometheus + simpleclient_dropwizard + 0.16.0 runtime - com.fasterxml.jackson.core - jackson-databind - 2.21.1 + io.prometheus + simpleclient_servlet_jakarta + 0.16.0 runtime - com.fasterxml.jackson.module - jackson-module-afterburner - 2.21.1 + io.prometheus + simpleclient_hotspot + 0.16.0 runtime - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.21.1 + io.prometheus + prometheus-metrics-exporter-opentelemetry + 1.8.0 + runtime + + + io.prometheus + prometheus-metrics-simpleclient-bridge + 1.8.0 runtime diff --git a/localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.jar b/localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.jar similarity index 96% rename from localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.jar rename to localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.jar index 9cd6b807..e4221e9b 100644 Binary files a/localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.jar and b/localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.jar differ diff --git a/localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.pom b/localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.pom similarity index 73% rename from localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.pom rename to localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.pom index e46dfa8f..a308d4ee 100644 --- a/localLibs/org/grobid/grobid-trainer/0.9.0/grobid-trainer-0.9.0.pom +++ b/localLibs/org/grobid/grobid-trainer/0.9.1/grobid-trainer-0.9.1.pom @@ -1,44 +1,25 @@ + + + + + 4.0.0 org.grobid grobid-trainer - 0.9.0 + 0.9.1 + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + - org.grobid - grobid-core - 0.9.0 - compile - - - xml-apis - xml-apis - 1.4.01 - compile - - - com.rockymadden.stringmetric - stringmetric-core_2.10 - 0.27.3 - compile - - - me.tongfei - progressbar - 0.9.5 - compile - - - org.slf4j - slf4j-api - 1.7.36 - compile - - - ch.qos.logback - logback-classic - 1.2.13 + org.jetbrains.kotlin + kotlin-stdlib + 2.2.20 compile @@ -83,16 +64,10 @@ 1.15.0 runtime - - commons-dbutils - commons-dbutils - 1.8.1 - runtime - com.google.guava guava - 33.5.0-jre + 33.6.0-jre runtime @@ -116,25 +91,61 @@ com.fasterxml.jackson.core jackson-core - 2.21.1 + 2.21.4 runtime com.fasterxml.jackson.core jackson-databind - 2.21.1 + 2.21.4 runtime - com.fasterxml.jackson.module - jackson-module-afterburner - 2.21.1 + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.21.4 runtime - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.21.1 + xml-apis + xml-apis + 1.4.01 + runtime + + + org.grobid + grobid-core + 0.9.1 + runtime + + + me.tongfei + progressbar + 0.9.5 + runtime + + + org.jline + jline + + + + + org.jline + jline-terminal + 3.21.0 + runtime + + + org.slf4j + slf4j-api + 2.0.17 + runtime + + + ch.qos.logback + logback-classic + 1.5.26 runtime diff --git a/resources/config/config-docker.yml b/resources/config/config-docker.yml index 9258c59e..113a195c 100644 --- a/resources/config/config-docker.yml +++ b/resources/config/config-docker.yml @@ -111,7 +111,6 @@ server: port: 8061 registerDefaultExceptionMappers: false maxThreads: 2048 - maxQueuedRequests: 2048 # change the following for having all http requests logged requestLog: diff --git a/resources/config/config.yml b/resources/config/config.yml index 1ae6fbf6..3ed5ba17 100644 --- a/resources/config/config.yml +++ b/resources/config/config.yml @@ -111,7 +111,6 @@ server: port: 8061 registerDefaultExceptionMappers: false maxThreads: 2048 - maxQueuedRequests: 2048 # change the following for having all http requests logged requestLog: diff --git a/src/main/java/org/grobid/service/SoftwareApplication.java b/src/main/java/org/grobid/service/SoftwareApplication.java index adcc6acc..5f0a8d4c 100644 --- a/src/main/java/org/grobid/service/SoftwareApplication.java +++ b/src/main/java/org/grobid/service/SoftwareApplication.java @@ -6,15 +6,16 @@ import io.dropwizard.core.setup.Bootstrap; import io.dropwizard.core.setup.Environment; import io.dropwizard.forms.MultiPartBundle; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.FilterRegistration; -import org.eclipse.jetty.servlets.CrossOriginFilter; -import org.eclipse.jetty.servlets.QoSFilter; +import org.eclipse.jetty.server.handler.CrossOriginHandler; +import org.eclipse.jetty.server.handler.QoSHandler; import org.grobid.service.configuration.SoftwareServiceConfiguration; import org.grobid.service.controller.HealthCheck; import ru.vyarus.dropwizard.guice.GuiceBundle; -import java.util.EnumSet; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; public class SoftwareApplication extends Application { private static final String RESOURCES = "/service"; @@ -45,26 +46,38 @@ public void run(SoftwareServiceConfiguration configuration, Environment environm environment.jersey().setUrlPattern(RESOURCES + "/*"); - String allowedOrigins = configuration.getCorsAllowedOrigins(); - String allowedMethods = configuration.getCorsAllowedMethods(); - String allowedHeaders = configuration.getCorsAllowedHeaders(); + // Enable CORS via Jetty 12's CrossOriginHandler (replaces the removed + // org.eclipse.jetty.servlets.CrossOriginFilter). Inserted above the application + // context so it applies to all served paths. + CrossOriginHandler cors = new CrossOriginHandler(); + cors.setAllowedOriginPatterns(toSet(configuration.getCorsAllowedOrigins())); + cors.setAllowedMethods(toSet(configuration.getCorsAllowedMethods())); + cors.setAllowedHeaders(toSet(configuration.getCorsAllowedHeaders())); + cors.setAllowCredentials(false); + environment.getApplicationContext().insertHandler(cors); - // Enable CORS headers - final FilterRegistration.Dynamic cors = - environment.servlets().addFilter("CORS", CrossOriginFilter.class); - - // CORS parameters - cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, allowedOrigins); - cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, allowedMethods); - cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, allowedHeaders); - - // Add URL mapping - cors.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); + // Limit concurrent requests via Jetty 12's QoSHandler (replaces the removed + // org.eclipse.jetty.servlets.QoSFilter). A non-positive value means unlimited. + int maxParallelRequests = configuration.getMaxParallelRequests(); + if (maxParallelRequests > 0) { + QoSHandler qos = new QoSHandler(); + qos.setMaxRequestCount(maxParallelRequests); + environment.getApplicationContext().insertHandler(qos); + } + } - // Enable QoS filter - final FilterRegistration.Dynamic qos = environment.servlets().addFilter("QOS", QoSFilter.class); - qos.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); - qos.setInitParameter("maxRequests", String.valueOf(configuration.getMaxParallelRequests())); + /** + * Splits a comma-separated config value (e.g. "OPTIONS,GET,POST") into a set of trimmed, + * non-empty entries preserving order, as expected by {@link CrossOriginHandler}. + */ + private static Set toSet(String csv) { + if (csv == null) { + return Set.of(); + } + return Arrays.stream(csv.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toCollection(LinkedHashSet::new)); } public static void main(String[] args) throws Exception {