diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..69bcdeab --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,22 @@ +name: Build + +on: + push: + paths: + - .github/workflows/build.yml + - build.gradle + - settings.gradle + - src/** + - launcher/build.gradle + - launcher/src/** + workflow_dispatch: + +jobs: + build: + runs-on: antville + + steps: + - uses: actions/checkout@v4 + + - name: Compile with Gradle + run: ./gradlew :compileJava diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5acc94a2..9eeb14b0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,15 +1,39 @@ -name: Deploy (Production) +name: Deploy -on: workflow_dispatch +on: + workflow_dispatch: + inputs: + hostname: + description: Hostname + type: string + required: true + default: antville.org jobs: - deploy: + stage: runs-on: antville environment: - name: antville.org - url: https://antville.org + name: production + url: ${{ inputs.hostname }} steps: - - name: Copy files to production server - run: ssh staging-server deploy-helma + - uses: actions/checkout@v4 + + - name: Build with Gradle + run: ./gradlew installDist + + - name: Copy build files to server + run: | + rsync ./build/install/helma/ ${{ inputs.hostname }}:./ \ + --verbose --archive --delete --compress \ + --filter '+ /bin' \ + --filter '+ /extras' \ + --filter '+ /launcher.jar' \ + --filter '- /lib/ext' \ + --filter '+ /lib' \ + --filter '+ /modules' \ + --filter '- /*' + + - name: Restart Helma + run: ssh ${{ inputs.hostname }} restart diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52994244..ac8a3d1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,16 +9,27 @@ permissions: contents: write jobs: - build: + release: runs-on: antville env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} LC_TIME: en_US.UTF-8 - TODAY: $(date +'%d %b %Y') steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create release notes + id: create_release_notes + run: | + release_notes=$(npx git-cliff@latest --latest) + # Write the release notes as a heredoc to the workflow output + # ⚠️ No white space around `<<` is crucial! + echo "release_notes<<.eot0x03" >> $GITHUB_OUTPUT + echo "$release_notes" >> $GITHUB_OUTPUT + echo ".eot0x03" >> $GITHUB_OUTPUT - name: Build with Gradle run: ./gradlew assembleDist @@ -29,23 +40,20 @@ jobs: direction: upload url: https://code.host.antville.org token: ${{ github.token }} - title: ${{ env.TODAY }} - #tag: $(date +'%Y.%m.%d') + title: Helma ${{ github.ref_name }} release-dir: build/distributions - release-notes-assistant: true + release-notes: ${{ steps.create_release_notes.outputs.release_notes }} verbose: true - name: Create release at GitHub - # FIXME: Currently only outputs gh command; adapt for Forgejo run: | - echo gh release create "$GITHUB_REF_NAME" \ + gh release create "$GITHUB_REF_NAME" \ --repo "$GITHUB_REPOSITORY" \ - --title "$(date +'%d %b %Y')" \ - --generate-notes + --title "Helma ${{ github.ref_name }}" \ + --notes "${{ steps.create_release_notes.outputs.release_notes }}" - name: Upload release assets to GitHub - # FIXME: Currently only outputs gh command; adapt for Forgejo run: | - echo gh release upload "$GITHUB_REF_NAME" \ - build/distributions/helma-*.* \ + gh release upload "$GITHUB_REF_NAME" build/distributions/helma-*.* \ + --repo "$GITHUB_REPOSITORY" \ --clobber diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml deleted file mode 100644 index 17e693d4..00000000 --- a/.github/workflows/stage.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deploy (Staging) - -on: workflow_dispatch - -jobs: - stage: - runs-on: antville - - environment: - name: stage - url: ${{ vars.stage_url }} - - steps: - - uses: actions/checkout@v4 - - - name: Build with Gradle - run: ./gradlew installDist - - - name: Publish to staging server - run: | - rsync ./build/install/helma/ staging-server:./ \ - --verbose --archive --delete --compress \ - --filter '+ /bin' \ - --filter '+ /extras' \ - --filter '+ /launcher.jar' \ - --filter '- /lib/ext' \ - --filter '+ /lib' \ - --filter '+ /modules' \ - --filter '- /*' - - - name: Restart Helma - run: ssh staging-server restart diff --git a/.gitignore b/.gitignore index e9557b6a..7bf7bcbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,30 @@ -.gradle -.idea -.settings -build +# Generally ignore hidden files +.* -/apps +# Manage some Codium configuration +.vscode/* +!.vscode +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json + +# Ignore files created during build or run /bin /backups -/db +build /docs -/extras /lib /licenses /log + +# Ignore files managed in src/dist +/*.properties +/apps +/db +/extras +/launcher.jar /static -/*.properties -/launcher.jar -/passwd -/start.* - +# Manage Gradle configuration !/gradle.properties diff --git a/.java-version b/.java-version index 3b5b5d8f..98d9bcb7 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -11.0 \ No newline at end of file +17 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..b9116072 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "Debug", + "request": "launch", + "mainClass": "helma.main.Server", + "projectName": "helma_" + } + ] +} diff --git a/LICENSE.md b/LICENSE.md index df5926e7..65dfb884 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # License -Copyright (c) 1999-2008 Helma Project. All rights reserved. +Copyright (c) 1999-2025 Helma Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/README.md b/README.md index e93af8d1..f6438677 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ## TL;DR -- Make sure you have Java 11 or higher installed -- Download and unpack the [latest release](https://github.com/antville/helma/releases) +- Make sure you have Java 17 or higher installed +- Download and unpack the [latest release](https://code.host.antville.org/antville/helma/releases) - Invoke `./bin/helma`, resp. `./bin/helma.bat`, depending on your platform - Direct your web browser to diff --git a/build.gradle b/build.gradle index abf4287f..cd60e214 100644 --- a/build.gradle +++ b/build.gradle @@ -18,8 +18,8 @@ allprojects { apply plugin: 'java' java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } repositories { @@ -42,15 +42,6 @@ if (JavaVersion.current().isJava8Compatible()) { } } -sourceSets { - main { - java { - // Sources in `src` will be available here after processing - srcDirs = ["$buildDir/src/main/java"] - } - } -} - configurations { // Wrapping implementation because it does not allow access to its files // (i.e. cannot be resolved) @@ -58,16 +49,17 @@ configurations { } dependencies { - implementation 'com.google.code.gson:gson:2.11.0' - implementation 'commons-codec:commons-codec:1.17.1' - implementation 'commons-fileupload:commons-fileupload:1.5' - implementation 'commons-logging:commons-logging:1.3.4' + implementation 'com.google.code.gson:gson:2.12.1' + implementation 'commons-codec:commons-codec:1.18.0' + implementation 'org.apache.commons:commons-fileupload2-core:2.0.0-M2' + implementation 'org.apache.commons:commons-fileupload2-jakarta:2.0.0-M1' + implementation 'commons-logging:commons-logging:1.3.5' implementation 'commons-net:commons-net:3.11.1' implementation 'com.sun.mail:javax.mail:1.6.2' - implementation 'javax.servlet:javax.servlet-api:4.0.1' + implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0' implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' - implementation 'org.eclipse.jetty:jetty-servlet:9.4.56.v20240826' - implementation 'org.eclipse.jetty:jetty-xml:9.4.56.v20240826' + implementation 'org.eclipse.jetty.ee9:jetty-ee9-servlet:12.0.19' + implementation 'org.eclipse.jetty:jetty-xml:12.0.19' implementation 'org.mozilla:rhino-all:1.8.0' implementation 'org.sejda.imageio:webp-imageio:0.1.6' implementation 'xerces:xercesImpl:2.12.2' @@ -156,9 +148,11 @@ installDist { dependsOn build } -tasks.register('processSource', Sync) { +def processSource = tasks.register('processSource', Sync) { def gitOutput = new ByteArrayOutputStream() + outputs.dir "${project.buildDir}/src" + exec { commandLine 'git', 'rev-parse', '--short', 'HEAD' standardOutput = gitOutput @@ -173,9 +167,11 @@ tasks.register('processSource', Sync) { .replaceAll('__builddate__', new Date().format("d MMM yyyy")) .replaceAll('__commithash__', gitOutput.toString().trim()) .replaceAll('__version__', version) - } into "${project.buildDir}/src" + } into outputs.files.singleFile } +tasks.compileJava.source = processSource.map { it.outputs.files } + tasks.register('update') { dependsOn installDist diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..755c4ca2 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,52 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +trim = true + +header = "## Changes" + +body = """ +{% for group, commits in commits | filter(attribute="merge_commit") | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + * [{{ commit.id | split(pat="") | slice(end=11) | join() }}]\ + (https://code.host.antville.org/antville/helma/commit/{{ commit.id }}) \ + {% if commit.breaking %}**Breaking:** {% endif %}\ + {{ commit.message | split(pat="\\n") | first | upper_first | escape }}\ + {% endfor %} +{% endfor %} + +**Full Changelog:** [{{ previous.version }} → {{ version }}]\ +(https://code.host.antville.org/antville/helma/compare/\ +{{ previous.version | urlencode }}..{{ version | urlencode }})\n\n +""" + +footer = """ +Generated by [git-cliff](https://git-cliff.org/). +""" + +[git] +conventional_commits = false +filter_commits = false +filter_unconventional = false +protect_breaking_commits = false +sort_commits = "newest" +split_commits = false +topo_order = false + +commit_parsers = [ + { message = "^Apply \\d+ suggestion", skip = true }, + { message = "^Merge .*(branch|dependabot|dependency|renovate)", skip = true }, + { message = "^Lock file maintenance", skip = true }, + { message = "yarn\\.lock", skip = true }, + + { message = "^[Ff]ix", group = " 🐛 Bug Fixes" }, + { field = "author.name", pattern = "[Rr]enovate|[Dd]ependabot", group = " 📦 Dependency Updates" }, + { message = "^Merge pull request", group = " 🔀 Merges" }, + { message = ".*", group = " Uncategorized" }, +] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b95..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793..002b867c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3b..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -205,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle index c6fe1184..5fd2dc60 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,6 @@ project(':modules').projectDir = file('modules/helma') project(':jala').projectDir = file('modules/jala') project(':hopKit').projectDir = file('modules/jala/util/HopKit') project(':test').projectDir = file('modules/jala/util/Test') + +// Rename this project to prevent redundancy and renaming of main project (VSC does not care, though) +project(':modules').name = 'modules' diff --git a/src/dist/extras/deploy.sh b/src/dist/extras/deploy.sh new file mode 100755 index 00000000..869cf945 --- /dev/null +++ b/src/dist/extras/deploy.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Use this script as forced command of an authorized SSH key: +# command="/home/helma/extras/deploy.sh" ssh-ed25519 AAAAC3NzaC… + +case "$SSH_ORIGINAL_COMMAND" in + ping) + echo pong + ;; + + restart) + printf 'Restarting Helma… ' + sudo /bin/systemctl restart helma + printf '%s\n' 'done.' + ;; + + *) + # Allow any rsync command but restrict it to the installation directory + rrsync -wo /home/helma + ;; +esac diff --git a/src/main/java/helma/framework/CookieTrans.java b/src/main/java/helma/framework/CookieTrans.java index fed45960..5293b843 100644 --- a/src/main/java/helma/framework/CookieTrans.java +++ b/src/main/java/helma/framework/CookieTrans.java @@ -17,7 +17,7 @@ package helma.framework; import java.io.Serializable; -import javax.servlet.http.Cookie; +import jakarta.servlet.http.Cookie; /** * Cookie Transmitter. A simple, serializable representation diff --git a/src/main/java/helma/framework/RequestBean.java b/src/main/java/helma/framework/RequestBean.java index 49091022..bf377a63 100644 --- a/src/main/java/helma/framework/RequestBean.java +++ b/src/main/java/helma/framework/RequestBean.java @@ -16,12 +16,12 @@ package helma.framework; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.Serializable; import java.util.Map; /** - * + * */ public class RequestBean implements Serializable { private static final long serialVersionUID = -6826881712426326687L; @@ -89,7 +89,7 @@ public class RequestBean implements Serializable { * @return the header value, or null */ public String getHeader(String name) { - return req.getHeader(name); + return req.getHeader(name); } /** diff --git a/src/main/java/helma/framework/RequestTrans.java b/src/main/java/helma/framework/RequestTrans.java index 83665ba8..ec8218fe 100644 --- a/src/main/java/helma/framework/RequestTrans.java +++ b/src/main/java/helma/framework/RequestTrans.java @@ -19,9 +19,9 @@ package helma.framework; import helma.util.SystemMap; import helma.util.StringUtils; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; import org.apache.commons.codec.binary.Base64; diff --git a/src/main/java/helma/framework/ResponseBean.java b/src/main/java/helma/framework/ResponseBean.java index c2790a34..061ecb54 100644 --- a/src/main/java/helma/framework/ResponseBean.java +++ b/src/main/java/helma/framework/ResponseBean.java @@ -19,7 +19,7 @@ package helma.framework; import helma.objectmodel.db.Transactor; import helma.scripting.ScriptingException; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.Serializable; import java.io.StringWriter; import java.io.PrintWriter; diff --git a/src/main/java/helma/framework/ResponseTrans.java b/src/main/java/helma/framework/ResponseTrans.java index af65a4c1..239487d7 100644 --- a/src/main/java/helma/framework/ResponseTrans.java +++ b/src/main/java/helma/framework/ResponseTrans.java @@ -21,7 +21,7 @@ import helma.framework.core.Application; import helma.util.*; import helma.scripting.ScriptingException; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.*; import java.security.*; import java.util.*; diff --git a/src/main/java/helma/main/ApplicationManager.java b/src/main/java/helma/main/ApplicationManager.java index 2deb88ee..e699a8d8 100644 --- a/src/main/java/helma/main/ApplicationManager.java +++ b/src/main/java/helma/main/ApplicationManager.java @@ -19,11 +19,13 @@ import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.xmlrpc.XmlRpcHandler; + import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.servlet.ServletHolder; +import org.eclipse.jetty.util.resource.ResourceFactory; import helma.framework.core.Application; import helma.framework.repository.FileRepository; @@ -481,23 +483,29 @@ public class ApplicationManager implements XmlRpcHandler { // if there is a static direcory specified, mount it if (this.staticDir != null) { + String staticPath = getAbsoluteFile(this.staticDir).getCanonicalPath(); - File staticContent = getAbsoluteFile(this.staticDir); - - getLogger().info("Serving static from " + staticContent.getPath()); + getLogger().info("Serving static from " + staticPath); getLogger().info("Mounting static at " + staticMountpoint); ResourceHandler rhandler = new ResourceHandler(); - rhandler.setResourceBase(staticContent.getPath()); + rhandler.setBaseResource(ResourceFactory.of(rhandler).newResource(staticPath)); rhandler.setWelcomeFiles(staticHome); - staticContext = ApplicationManager.this.context.addContext(staticMountpoint, ""); //$NON-NLS-1$ + ContextHandler staticContext = new ContextHandler(); + staticContext.setContextPath(staticMountpoint); staticContext.setHandler(rhandler); + ApplicationManager.this.context.addHandler(staticContext); staticContext.start(); } - appContext = new ServletContextHandler(context, pathPattern, true, true); + // I hope I am correct assuming Helma does not need Jetty’s session management, but using + // `ServletContextHandler.SESSIONS` causes an exception: Shared scheduler not started + appContext = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); + appContext.setContextPath(pathPattern); + context.addHandler(appContext); + Class servletClass = servletClassName == null ? EmbeddedServletClient.class : Class.forName(servletClassName); ServletHolder holder = new ServletHolder(servletClass); @@ -529,10 +537,9 @@ public class ApplicationManager implements XmlRpcHandler { } if (protectedStaticDir != null) { - File protectedContent = getAbsoluteFile(protectedStaticDir); - appContext.setResourceBase(protectedContent.getPath()); - getLogger().info("Serving protected static from " + - protectedContent.getPath()); + String protectedContent = getAbsoluteFile(protectedStaticDir).getCanonicalPath(); + appContext.setBaseResourceAsString(protectedContent); + getLogger().info("Serving protected static from " + protectedContent); } // Remap the context paths and start @@ -556,7 +563,9 @@ public class ApplicationManager implements XmlRpcHandler { // unbind from Jetty HTTP server if (ApplicationManager.this.jetty != null) { if (this.appContext != null) { - ApplicationManager.this.context.removeHandler(this.appContext); + // Adding appContext to the ContextHandlerCollection works (see above) but removing it causes an exception of + // incompatible types: ServletContextHandler cannot be converted to Handler + //ApplicationManager.this.context.removeHandler(this.appContext); this.appContext.stop(); this.appContext.destroy(); this.appContext = null; diff --git a/src/main/java/helma/main/JettyServer.java b/src/main/java/helma/main/JettyServer.java index 88ba8c10..83e09bcf 100644 --- a/src/main/java/helma/main/JettyServer.java +++ b/src/main/java/helma/main/JettyServer.java @@ -16,11 +16,12 @@ package helma.main; - import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.URLResourceFactory; import org.eclipse.jetty.xml.XmlConfiguration; import java.net.URL; @@ -36,18 +37,20 @@ public class JettyServer { public static JettyServer init(Server server, ServerConfig config) throws IOException { File configFile = config.getConfigFile(); if (configFile != null && configFile.exists()) { - return new JettyServer(configFile.toURI().toURL()); + URLResourceFactory resourceFactory = new URLResourceFactory(); + Resource resource = resourceFactory.newResource(configFile.toURI()); + return new JettyServer(resource); } else if (config.hasWebsrvPort()) { return new JettyServer(config.getWebsrvPort(), server); } return null; } - private JettyServer(URL url) throws IOException { + private JettyServer(Resource resource) throws IOException { http = new org.eclipse.jetty.server.Server(); try { - XmlConfiguration config = new XmlConfiguration(url); + XmlConfiguration config = new XmlConfiguration(resource); config.configure(http); } catch (IOException e) { @@ -59,7 +62,7 @@ public class JettyServer { private JettyServer(InetSocketAddress webPort, Server server) throws IOException { - + http = new org.eclipse.jetty.server.Server(); // start embedded web server if port is specified @@ -73,7 +76,6 @@ public class JettyServer { connector.setHost(webPort.getAddress().getHostAddress()); connector.setPort(webPort.getPort()); connector.setIdleTimeout(30000); - connector.setSoLingerTime(-1); connector.setAcceptorPriorityDelta(0); connector.setAcceptQueueSize(0); @@ -100,12 +102,13 @@ public class JettyServer { } private void openListeners() throws IOException { - // opening the listener here allows us to run on priviledged port 80 under jsvc + // opening the listener here allows us to run on privileged port 80 under jsvc // even as non-root user, because init() is called with root privileges // while start() will be called with the user we will actually run as - Connector[] connectors = http.getConnectors(); - for (int i = 0; i < connectors.length; i++) { - ((ServerConnector) connectors[i]).open(); + for (var connector : http.getConnectors()) { + if (connector instanceof ServerConnector) { + ((ServerConnector) connector).open(); + } } } } diff --git a/src/main/java/helma/main/Server.java b/src/main/java/helma/main/Server.java index 88d70db7..d951adae 100644 --- a/src/main/java/helma/main/Server.java +++ b/src/main/java/helma/main/Server.java @@ -21,6 +21,7 @@ import helma.framework.repository.FileResource; import helma.framework.core.*; import helma.objectmodel.db.DbSource; import helma.util.*; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlrpc.*; @@ -152,8 +153,8 @@ public class Server implements Runnable { String javaVersion = System.getProperty("java.version", "0"); int majorVersion = Integer.parseInt(javaVersion.split("\\.")[0]); - if (majorVersion < 11) { - System.err.println("This version of Helma requires Java 11 or greater."); + if (majorVersion < 17) { + System.err.println("This version of Helma requires Java 17 or greater."); if (majorVersion == 0) { // don't think this will ever happen, but you never know System.err.println("Your Java Runtime did not provide a version number. Please update to a more recent version."); diff --git a/src/main/java/helma/servlet/AbstractServletClient.java b/src/main/java/helma/servlet/AbstractServletClient.java index 3e3b3f59..546a0698 100644 --- a/src/main/java/helma/servlet/AbstractServletClient.java +++ b/src/main/java/helma/servlet/AbstractServletClient.java @@ -22,18 +22,29 @@ package helma.servlet; import helma.framework.*; import helma.framework.core.Application; import helma.util.*; + import java.io.*; -import java.util.*; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; -import javax.servlet.*; -import javax.servlet.http.*; +import java.util.*; + +import jakarta.servlet.*; +import jakarta.servlet.http.*; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.*; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.apache.commons.fileupload.servlet.ServletRequestContext; + +import org.apache.commons.fileupload2.core.DiskFileItemFactory; +import org.apache.commons.fileupload2.core.FileItem; +import org.apache.commons.fileupload2.core.FileUploadException; +import org.apache.commons.fileupload2.core.FileUploadSizeException; +import org.apache.commons.fileupload2.core.ProgressListener; + +import org.apache.commons.fileupload2.jakarta.JakartaServletDiskFileUpload; +import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload; +import org.apache.commons.fileupload2.jakarta.JakartaServletRequestContext; /** * This is an abstract Hop servlet adapter. This class communicates with hop applications @@ -218,9 +229,9 @@ public abstract class AbstractServletClient extends HttpServlet { // read file uploads List uploads = null; - ServletRequestContext reqcx = new ServletRequestContext(request); + JakartaServletRequestContext reqcx = new JakartaServletRequestContext(request); - if (ServletFileUpload.isMultipartContent(reqcx)) { + if (JakartaServletFileUpload.isMultipartContent(reqcx)) { // get session for upload progress monitoring UploadStatus uploadStatus = getApplication().getUploadStatus(reqtrans); try { @@ -228,7 +239,7 @@ public abstract class AbstractServletClient extends HttpServlet { } catch (Exception upx) { log("Error in file upload", upx); String message; - boolean tooLarge = (upx instanceof FileUploadBase.SizeLimitExceededException); + boolean tooLarge = (upx instanceof FileUploadSizeException); if (tooLarge) { message = "File upload size exceeds limit of " + uploadLimit + " kB"; } else { @@ -653,12 +664,12 @@ public abstract class AbstractServletClient extends HttpServlet { map.put(name, newValues); } - protected List parseUploads(ServletRequestContext reqcx, RequestTrans reqtrans, + protected List parseUploads(JakartaServletRequestContext reqcx, RequestTrans reqtrans, final UploadStatus uploadStatus, String encoding) - throws FileUploadException, UnsupportedEncodingException { + throws FileUploadException, UnsupportedCharsetException, IOException { // handle file upload - DiskFileItemFactory factory = new DiskFileItemFactory(); - FileUpload upload = new FileUpload(factory); + DiskFileItemFactory factory = DiskFileItemFactory.builder().get(); + JakartaServletFileUpload upload = new JakartaServletFileUpload(factory); // use upload limit for individual file size, but also set a limit on overall size upload.setFileSizeMax(uploadLimit * 1024); upload.setSizeMax(totalUploadLimit * 1024); @@ -681,7 +692,7 @@ public abstract class AbstractServletClient extends HttpServlet { Object value; // check if this is an ordinary HTML form element or a file upload if (item.isFormField()) { - value = item.getString(encoding); + value = item.getString(Charset.forName(encoding)); } else { value = new MimePart(item); } diff --git a/src/main/java/helma/servlet/EmbeddedServletClient.java b/src/main/java/helma/servlet/EmbeddedServletClient.java index 80b3cd6e..9f9a37cc 100644 --- a/src/main/java/helma/servlet/EmbeddedServletClient.java +++ b/src/main/java/helma/servlet/EmbeddedServletClient.java @@ -19,7 +19,7 @@ package helma.servlet; import helma.framework.*; import helma.framework.core.Application; import helma.main.*; -import javax.servlet.*; +import jakarta.servlet.*; /** * Servlet client that runs a Helma application for the embedded diff --git a/src/main/java/helma/servlet/StandaloneServletClient.java b/src/main/java/helma/servlet/StandaloneServletClient.java index 5e1af952..bdfeeeae 100644 --- a/src/main/java/helma/servlet/StandaloneServletClient.java +++ b/src/main/java/helma/servlet/StandaloneServletClient.java @@ -23,7 +23,7 @@ import helma.main.ServerConfig; import helma.main.Server; import java.io.*; -import javax.servlet.*; +import jakarta.servlet.*; import java.util.*; /** @@ -98,7 +98,7 @@ public final class StandaloneServletClient extends AbstractServletClient { repositoryImpl = "helma.framework.repository.FileRepository"; } } - + try { Repository newRepository = (Repository) Class.forName(repositoryImpl) .getConstructor(parameters) @@ -116,7 +116,7 @@ public final class StandaloneServletClient extends AbstractServletClient { } } } - + // add app dir FileRepository appRep = new FileRepository(appDir); log("adding repository: " + appDir); diff --git a/src/main/java/helma/util/MimePart.java b/src/main/java/helma/util/MimePart.java index f69f1d61..e4ed29d8 100644 --- a/src/main/java/helma/util/MimePart.java +++ b/src/main/java/helma/util/MimePart.java @@ -16,7 +16,7 @@ package helma.util; -import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload2.core.FileItem; import java.io.*; import java.util.Date; @@ -238,7 +238,7 @@ public class MimePart implements Serializable { file = new File(base, filename); if (fileItem != null) { - fileItem.write(file); + fileItem.write(file.toPath()); // null out fileItem, since calling write() may have moved the temp file fileItem = null; } else { @@ -249,7 +249,7 @@ public class MimePart implements Serializable { // return file name return filename; } catch (Exception x) { - System.err.println("Error in MimePart.writeToFile(): " + x); + System.err.println("Error in MimePart.writeToFile(): " + x); return null; } }