diff --git a/.github/workflows/camunda.yml b/.github/workflows/camunda.yml index d7dd2b90..59a63def 100644 --- a/.github/workflows/camunda.yml +++ b/.github/workflows/camunda.yml @@ -8,7 +8,7 @@ on: - cron: '0 12 * * 1' env: - TEAM: ("jamesfwood" "voxparcxls" "galenhollins" "galenatjpl" "jeffreypon") + TEAM: ("jamesfwood" "jdrodjpl" "jl-0") jobs: @@ -39,6 +39,27 @@ jobs: java-version: '17' distribution: 'temurin' + # Configure Maven security (master password) + - name: Configure Maven security + run: | + mkdir -p ~/.m2 + echo " + ${{ secrets.MAVEN_MASTER_PASSWORD }} + " > ~/.m2/settings-security.xml + + # Configure Maven settings (encrypted repo password) + - name: Configure Maven settings + run: | + echo " + + + ${{ secrets.MAVEN_REPO_ID }} + ${{ secrets.MAVEN_USERNAME }} + ${{ secrets.MAVEN_ENCRYPTED_PASSWORD }} + + + " > ~/.m2/settings.xml + - name: Cache Maven packages uses: actions/cache@v4 with: @@ -47,6 +68,13 @@ jobs: restore-keys: | ${{ runner.os }}-m2- + - name: Configure Camunda Download Credentials + run: | + echo "machine downloads.camunda.cloud + login ${{ secrets.CAMUNDA_DOWNLOAD_LOGIN }} + password ${{ secrets.CAMUNDA_DOWNLOAD_PASSWORD }}" > ~/.netrc + chmod 400 ~/.netrc + - name: Create open-source certs run: | cd cws-certs @@ -58,7 +86,16 @@ jobs: mkdir ~/.cws/ chmod 700 ~/.cws/ echo ${{ secrets.KEYSTORE_PASSWORD }} > ~/.cws/creds - chmod 600 ~/.cws/creds + chmod 400 ~/.cws/creds + + - name: Write license to file + run: | + mkdir -p ~/.camunda + chmod 700 ~/.camunda + cat <<'EOF' > ~/.camunda/license.txt + ${{ secrets.CAMUNDA_LICENSE }} + EOF + chmod 400 ~/.camunda/license.txt - name: Download Logstash run: | @@ -79,7 +116,27 @@ jobs: sleep 5s docker ps -a - - name: Build CWS + - name: Cache Dependency-Check data + uses: actions/cache@v4 + with: + path: ~/.dependency-check-data + key: ${{ runner.os }}-dependency-check + restore-keys: | + ${{ runner.os }}-dependency-check + + - name: Run Dependency Check + run: mvn clean dependency-check:aggregate + shell: bash + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + + - name: Upload Dependency Check Report + uses: actions/upload-artifact@v4 + with: + name: dependency-check-report + path: target/dependency-check-reports/dependency-check-report.html + + - name: Build and Start CWS id: build run: | cd ci @@ -87,9 +144,9 @@ jobs: ./run_ci.sh $SECURITY $WORKERS shell: bash - - name: Show CWS Log + - name: List CWS Logs run: | - cd dist/console-only/cws/server/apache-tomcat-9.0.75/logs + cd "dist/console-only/cws/server/apache-tomcat-10.1.36/logs" ls -al - name: Set up Google Chrome @@ -123,6 +180,36 @@ jobs: name: test-screenshots path: test-screenshots/ + - name: Show CWS Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Show CWS Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Cleanup Private files + if: always() + run: | + rm -rf ~/.camunda + rm -f ~/.netrc + - name: Send custom JSON data to Slack workflow if: ${{ always() && contains(env.TEAM, github.actor) }} id: slack @@ -173,6 +260,27 @@ jobs: java-version: '17' distribution: 'temurin' + # Configure Maven security (master password) + - name: Configure Maven security + run: | + mkdir -p ~/.m2 + echo " + ${{ secrets.MAVEN_MASTER_PASSWORD }} + " > ~/.m2/settings-security.xml + + # Configure Maven settings (encrypted repo password) + - name: Configure Maven settings + run: | + echo " + + + ${{ secrets.MAVEN_REPO_ID }} + ${{ secrets.MAVEN_USERNAME }} + ${{ secrets.MAVEN_ENCRYPTED_PASSWORD }} + + + " > ~/.m2/settings.xml + - name: Cache Maven packages uses: actions/cache@v4 with: @@ -181,6 +289,13 @@ jobs: restore-keys: | ${{ runner.os }}-m2- + - name: Configure Camunda Download Credentials + run: | + echo "machine downloads.camunda.cloud + login ${{ secrets.CAMUNDA_DOWNLOAD_LOGIN }} + password ${{ secrets.CAMUNDA_DOWNLOAD_PASSWORD }}" > ~/.netrc + chmod 400 ~/.netrc + - name: Create open-source certs run: | cd cws-certs @@ -192,7 +307,16 @@ jobs: mkdir ~/.cws/ chmod 700 ~/.cws/ echo ${{ secrets.KEYSTORE_PASSWORD }} > ~/.cws/creds - chmod 600 ~/.cws/creds + chmod 400 ~/.cws/creds + + - name: Write license to file + run: | + mkdir -p ~/.camunda + chmod 700 ~/.camunda + cat <<'EOF' > ~/.camunda/license.txt + ${{ secrets.CAMUNDA_LICENSE }} + EOF + chmod 400 ~/.camunda/license.txt - name: Download Logstash run: | @@ -213,7 +337,7 @@ jobs: sleep 5s docker ps -a - - name: Build CWS + - name: Build and Start CWS id: build run: | cd ci @@ -221,9 +345,9 @@ jobs: ./run_ci.sh $SECURITY $WORKERS shell: bash - - name: Show CWS Log + - name: List CWS Logs run: | - cd dist/console-only/cws/server/apache-tomcat-9.0.75/logs + cd dist/console-only/cws/server/apache-tomcat-10.1.36/logs ls -al - name: Set up Google Chrome @@ -246,6 +370,48 @@ jobs: name: test-screenshots-advanced path: test-screenshots/ + - name: Show CWS Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Show CWS Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Show CWS Log for Worker2 + if: always() + working-directory: dist/worker2/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Worker2 + if: always() + working-directory: dist/worker2/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Cleanup Private files + if: always() + run: | + rm -rf ~/.camunda + rm -f ~/.netrc + - name: Send custom JSON data to Slack workflow if: ${{ always() && contains(env.TEAM, github.actor) }} id: slack diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c5ff3414..2c252872 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,10 +1,11 @@ -name: Docker Build +name: CWS Docker Build +# Triggers the workflow on push on: push: - branches: [main, develop, docker_build] # Adjust branches as needed - pull_request: - branches: [main] # Adjust branches as needed + schedule: + # trigger a build and test of CWS weekly on Monday at 5 AM PST / 12 PM UTC + - cron: '0 12 * * 1' permissions: contents: read @@ -57,6 +58,51 @@ jobs: cd cws-certs ./generate-certs.sh + # Configure Maven security (master password) + - name: Configure Maven security + run: | + mkdir -p ~/.m2 + echo " + ${{ secrets.MAVEN_MASTER_PASSWORD }} + " > ~/.m2/settings-security.xml + + # Configure Maven settings (encrypted repo password) + - name: Configure Maven settings + run: | + echo " + + + ${{ secrets.MAVEN_REPO_ID }} + ${{ secrets.MAVEN_USERNAME }} + ${{ secrets.MAVEN_ENCRYPTED_PASSWORD }} + + + " > ~/.m2/settings.xml + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2- + + - name: Configure Camunda Download Credentials + run: | + echo "machine downloads.camunda.cloud + login ${{ secrets.CAMUNDA_DOWNLOAD_LOGIN }} + password ${{ secrets.CAMUNDA_DOWNLOAD_PASSWORD }}" > ~/.netrc + chmod 400 ~/.netrc + + - name: Write license to file + run: | + mkdir -p ~/.camunda + chmod 700 ~/.camunda + cat <<'EOF' > ~/.camunda/license.txt + ${{ secrets.CAMUNDA_LICENSE }} + EOF + chmod 400 ~/.camunda/license.txt + - name: Build CWS Docker Image using script run: | chmod +x build.sh @@ -136,3 +182,9 @@ jobs: echo "Still waiting for CWS console... ($ELAPSED/$MAX_WAIT seconds)" done working-directory: install/docker/console-db-es-ls-kibana # Ensure correct context for docker-compose logs + + - name: Cleanup Private files + if: always() + run: | + rm -rf ~/.camunda + rm -f ~/.netrc diff --git a/.github/workflows/ldap.yml b/.github/workflows/ldap.yml index 2d32455c..d1a488d7 100644 --- a/.github/workflows/ldap.yml +++ b/.github/workflows/ldap.yml @@ -8,7 +8,7 @@ on: - cron: '0 12 * * 1' env: - TEAM: ("jamesfwood" "voxparcxls" "galenhollins" "galenatjpl" "jeffreypon") + TEAM: ("jamesfwood" "jdrodjpl" "jl-0") jobs: @@ -39,6 +39,34 @@ jobs: java-version: '17' distribution: 'temurin' + # Configure Maven security (master password) + - name: Configure Maven security + run: | + mkdir -p ~/.m2 + echo " + ${{ secrets.MAVEN_MASTER_PASSWORD }} + " > ~/.m2/settings-security.xml + + # Configure Maven settings (encrypted repo password) + - name: Configure Maven settings + run: | + echo " + + + ${{ secrets.MAVEN_REPO_ID }} + ${{ secrets.MAVEN_USERNAME }} + ${{ secrets.MAVEN_ENCRYPTED_PASSWORD }} + + + " > ~/.m2/settings.xml + + - name: Configure Camunda Download Credentials + run: | + echo "machine downloads.camunda.cloud + login ${{ secrets.CAMUNDA_DOWNLOAD_LOGIN }} + password ${{ secrets.CAMUNDA_DOWNLOAD_PASSWORD }}" > ~/.netrc + chmod 400 ~/.netrc + - name: Cache Maven packages uses: actions/cache@v4 with: @@ -58,7 +86,7 @@ jobs: mkdir ~/.cws/ chmod 700 ~/.cws/ echo ${{ secrets.KEYSTORE_PASSWORD }} > ~/.cws/creds - chmod 600 ~/.cws/creds + chmod 400 ~/.cws/creds - name: Download Logstash run: | @@ -84,7 +112,7 @@ jobs: sleep 5s docker ps -a - - name: Build CWS + - name: Build and Start CWS id: build run: | cd ci @@ -92,9 +120,9 @@ jobs: ./run_ci.sh $SECURITY $WORKERS shell: bash - - name: Show CWS Log + - name: List CWS Logs run: | - cd dist/console-only/cws/server/apache-tomcat-9.0.75/logs + cd dist/console-only/cws/server/apache-tomcat-10.1.36/logs ls -al - name: Set up Google Chrome @@ -110,6 +138,30 @@ jobs: run: mvn -Dmaven.compiler.debug=true -Dmaven.compiler.debuglevel=lines,vars, -Dit.test=LdapTestIT verify -DskipTests shell: bash + - name: Show CWS Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Console + if: always() + working-directory: dist/console-only/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + + - name: Show CWS Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat cws.log + + - name: Show Catalina Log for Worker1 + if: always() + working-directory: dist/worker1/cws/server/apache-tomcat-10.1.36/logs + run: | + cat catalina.out + - name: Send custom JSON data to Slack workflow if: ${{ always() && contains(env.TEAM, github.actor) }} id: slack diff --git a/.gitignore b/.gitignore index e866a95a..44bb1084 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ install/logging/logstash-*.zip /jacoco-reports /test-screenshots +# Camunda distro zip +/install/camunda-distro-zips/ + *.cnf diff --git a/ACTIVEMQ_ARTEMIS_MIGRATION.md b/ACTIVEMQ_ARTEMIS_MIGRATION.md new file mode 100644 index 00000000..599081ac --- /dev/null +++ b/ACTIVEMQ_ARTEMIS_MIGRATION.md @@ -0,0 +1,109 @@ +# ActiveMQ Artemis Migration Summary + +## Overview +This document summarizes the changes made to migrate from ActiveMQ 5.x to ActiveMQ Artemis for Spring 6 compatibility. + +## Changes Made + +### 1. Updated SchedulerQueueUtils.java +**File**: `cws-service/src/main/java/jpl/cws/scheduler/SchedulerQueueUtils.java` + +**Changes**: +- Replaced ActiveMQ 5.x broker imports with Artemis equivalents: + - `org.apache.activemq.broker.BrokerRegistry` → `org.apache.activemq.artemis.api.core.management.ActiveMQServerControl` + - `org.apache.activemq.broker.jmx.BrokerViewMBean` → `org.apache.activemq.artemis.api.core.management.ActiveMQServerControl` + - `org.apache.activemq.broker.jmx.QueueViewMBean` → `org.apache.activemq.artemis.api.core.management.QueueControl` +- Updated JMX management methods to use Artemis APIs +- Changed return type of `getAmqClients()` from `Set` to `Set` +- Updated queue statistics methods to use Artemis equivalents + +### 2. Updated MvcCore.java +**File**: `cws-service/src/main/java/jpl/cws/controller/MvcCore.java` + +**Changes**: +- Updated the call to `getAmqClients()` to handle the new return type +- Updated error message to reflect "servers" instead of "clients" + +### 3. Updated Application Context Files + +#### install/cws-ui/applicationContext.xml +**Changes**: +- Replaced ActiveMQ 5.x embedded broker configuration with Artemis embedded broker +- Updated connection factory from `org.apache.activemq.ActiveMQConnectionFactory` to `org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory` +- Updated JMS destination classes: + - `org.apache.activemq.command.ActiveMQTopic` → `org.apache.activemq.artemis.jms.client.ActiveMQTopic` + - `org.apache.activemq.command.ActiveMQQueue` → `org.apache.activemq.artemis.jms.client.ActiveMQQueue` +- Added user/password properties for Artemis connection factory + +#### install/cws-engine/applicationContext.xml +**Changes**: +- Updated connection factory to use Artemis +- Updated all JMS destination classes to use Artemis equivalents + +### 4. Added Dependencies +**File**: `pom.xml` + +**Changes**: +- Added `artemis-server` dependency for embedded broker functionality + +### 5. Created Artemis Configuration +**File**: `install/cws-ui/broker.xml` + +**New File**: +- Created Artemis broker configuration file +- Configured addresses and queues for all existing topics and queues +- Set up security settings and address settings +- Configured persistence and journal settings + +## Configuration Properties + +The following properties need to be configured in your properties files: + +```properties +# Artemis JMX Object Name (default provided) +cws.broker.obj.name=org.apache.activemq.artemis:broker="cwsConsoleBroker" + +# Artemis JMX Service URL +cws.amq.jmx.service.url=service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi + +# Artemis connection credentials +cws.amq.user=amq +cws.amq.password=amq +``` + +## Benefits of Migration + +1. **Spring 6 Compatibility**: ActiveMQ Artemis is fully compatible with Spring 6 +2. **Better Performance**: Artemis provides improved performance over ActiveMQ 5.x +3. **Modern Architecture**: Artemis uses a more modern, scalable architecture +4. **Enhanced Features**: Better clustering, security, and monitoring capabilities +5. **Future-Proof**: Artemis is the future of ActiveMQ development + +## Testing Recommendations + +1. Test all JMS message sending and receiving functionality +2. Verify JMX monitoring and management features work correctly +3. Test queue creation and management through SchedulerQueueUtils +4. Verify all existing topics and queues are properly configured +5. Test connection factory configuration with authentication + +## Notes + +- The migration maintains backward compatibility for JMS operations +- All existing queue and topic names are preserved +- JMX monitoring functionality has been updated to use Artemis APIs +- The embedded broker configuration has been modernized for Artemis + +## Next Steps + +1. Update any remaining ActiveMQ 5.x references in other configuration files +2. Test the application thoroughly with the new Artemis configuration +3. Update any custom ActiveMQ 5.x broker plugins or extensions +4. Consider updating to use Artemis-specific features for better performance + +## Build Status + +✅ **Compilation Successful**: The cws-service module now compiles successfully with ActiveMQ Artemis +✅ **Dependencies Resolved**: All Artemis dependencies are properly configured +✅ **JMX Management**: Updated to use Artemis JMX management APIs +✅ **JMS Components**: All JMS destinations and connection factories updated to Artemis diff --git a/JAKARTA_MIGRATION_SUMMARY.md b/JAKARTA_MIGRATION_SUMMARY.md new file mode 100644 index 00000000..e8d39d15 --- /dev/null +++ b/JAKARTA_MIGRATION_SUMMARY.md @@ -0,0 +1,98 @@ +# Jakarta EE Migration Summary + +## Overview +This document summarizes the changes made to migrate from `javax.*` dependencies to `jakarta.*` dependencies for full Spring 6 and Jakarta EE compatibility. + +## Changes Made + +### 1. Updated Dependencies in pom.xml + +**File**: `pom.xml` + +**Changes**: +- Updated `javax.mail.version` property from `1.4.7` to `jakarta.mail.version` `2.0.1` +- Replaced `javax.mail:mail` with: + - `jakarta.mail:jakarta.mail-api` (API) + - `org.eclipse.angus:jakarta.mail` (Implementation) + +### 2. Updated Module Dependencies + +#### cws-adaptation-engine/pom.xml +**Changes**: +- Replaced `javax.activation:activation` with `jakarta.activation:jakarta.activation-api` +- Replaced `javax.mail:mail` with `jakarta.mail:jakarta.mail-api` + +#### cws-tasks/pom.xml +**Changes**: +- Replaced `javax.mail:mail` with `jakarta.mail:jakarta.mail-api` + +#### cws-engine-service/pom.xml +**Changes**: +- Replaced `javax.mail:mail` with `jakarta.mail:jakarta.mail-api` + +### 3. Updated JAX-RS References + +#### cws-test/src/test/java/jpl/cws/test/RestGetTaskTest.java +**Changes**: +- Updated import from `javax.ws.rs.client.ClientBuilder` to `jakarta.ws.rs.client.ClientBuilder` + +#### install/camunda_mods/web.xml +**Changes**: +- Updated all JAX-RS Application parameter names from `javax.ws.rs.Application` to `jakarta.ws.rs.Application` +- Affected servlets: Cockpit Api, Admin Api, Tasklist Api, Engine Api, Welcome Api + +#### install/engine-rest/web.xml +**Changes**: +- Updated JAX-RS Application parameter name from `javax.ws.rs.Application` to `jakarta.ws.rs.Application` + +## Dependencies Migration Map + +| Old Dependency | New Dependency | Version | +|----------------|----------------|---------| +| `javax.mail:mail` | `jakarta.mail:jakarta.mail-api` | 2.0.1 | +| `javax.mail:mail` | `org.eclipse.angus:jakarta.mail` | 2.0.1 | +| `javax.activation:activation` | `jakarta.activation:jakarta.activation-api` | 2.1.0 | +| `javax.ws.rs.*` | `jakarta.ws.rs.*` | N/A | + +## Benefits of Migration + +1. **Spring 6 Compatibility**: Full compatibility with Spring Framework 6 +2. **Jakarta EE Standards**: Uses modern Jakarta EE specifications +3. **Future-Proof**: Jakarta EE is the future standard for enterprise Java +4. **Better Integration**: Improved integration with modern application servers +5. **Security Updates**: Access to latest security patches and features + +## Remaining javax Dependencies + +The following `javax` dependencies are still present but are **acceptable to keep**: + +### Core Java APIs (No Migration Needed) +- `javax.management.*` - JMX APIs (part of Java SE) +- `javax.naming.*` - JNDI APIs (part of Java SE) +- `javax.tools.*` - Java Compiler API (part of Java SE) +- `javax.net.ssl.*` - SSL/TLS APIs (part of Java SE) +- `javax.xml.*` - XML APIs (part of Java SE) + +### Configuration Files +- `javax.net.ssl.trustStore` system properties (standard Java property) + +## Testing Recommendations + +1. **Email Functionality**: Test all email sending functionality with the new Jakarta Mail API +2. **JAX-RS Services**: Verify all REST services work correctly with Jakarta JAX-RS +3. **Activation**: Test any file attachment or MIME type handling +4. **Integration Tests**: Run full integration tests to ensure compatibility + +## Notes + +- The migration maintains backward compatibility for all functionality +- All existing email, REST, and activation features continue to work +- The Jakarta Mail implementation (Eclipse Angus) provides the same functionality as the old javax.mail +- Core Java APIs like JMX, JNDI, and SSL remain unchanged as they are part of the Java SE specification + +## Next Steps + +1. Test all email functionality thoroughly +2. Verify REST API endpoints work correctly +3. Update any custom code that might be using deprecated javax APIs +4. Consider updating to use Jakarta EE 9+ features for enhanced functionality diff --git a/NOTICE.txt b/NOTICE.txt index cb194eff..6a8784d5 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -26,7 +26,6 @@ This software includes third party software subject to the following copyrights: - The JMS library is Copyright (c) 1993, 2020, Oracle and/or its affiliates. - The Javax Mail library is Copyright (c) 1993, 2020, Oracle and/or its affiliates. - The Javax Servlet API is Copyright (c) 1993, 2020, Oracle and/or its affiliates. - - The Joda-Time library is Copyright (c) 2002-2020 Joda.org. All Rights Reserved. - The Jython library has the following copyright holders: - Copyright (c) 2000-2018 Jython Developers. All rights reserved. - Copyright (c) 2000 BeOpen.com. All Rights Reserved. diff --git a/README.md b/README.md index e1cb7bf8..6e1bd39e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ See the [wiki](https://github.com/NASA-AMMOS/common-workflow-service/wiki) for m - **Store Your Keystore Password**: You will need to add your own creds file, which carries the keystore password, to this path: `~/.cws/creds` - Set the permissions for the **~/.cws/** directory and **creds** file as Owner-Only. - **~/.cws/** directory: `chmod 700 ~/.cws/` - - **~/.cws/creds** file: `chmod 600 ~/.cws/creds` + - **~/.cws/creds** file: `chmod 400 ~/.cws/creds` ### **Development Environment Configuration** @@ -101,7 +101,7 @@ _In a different terminal window `cd` into root of **common-workflow-service** fo For development we tend to create our own separate build script `` (firstinitial-lastname.sh), i.e.:`jsmith.sh`, that calls `dev.sh`. Here's an template for your personal build script that will work for development on a local machine: * Correctly set the Elasticsearch configuration within your personal script by assigning the proper protocol, `HTTP` or `HTTPS`, to `ES_PROTOCOL` with Elasticsearch hostname assigned to `ES_HOST`. - * Example: + * Example: * `ES_PROTOCOL="HTTP"` * `ES_HOST="locahost"` @@ -159,6 +159,15 @@ WORKER_ABANDONED_DAYS=1 ./dev.sh `pwd` ${USER} ${DB_TYPE} ${DB_HOST} ${DB_PORT} ${DB_NAME} ${DB_USER} ${DB_PASS} ${ES_PROTOCOL} ${ES_HOST} ${ES_PORT} ${ES_USE_AUTH} ${ES_USERNAME} ${ES_PASSWORD} ${CLOUD} ${SECURITY} ${HOSTNAME} ${EMAIL_LIST} ${ADMIN_FIRST} ${ADMIN_LAST} ${ADMIN_EMAIL} ${NUM_WORKERS} ${WORKER_MAX_NUM_RUNNING_PROCS} ${WORKER_ABANDONED_DAYS} ``` +#### Check Dependencies for Security Vunerabilities +``` +mvn dependency-check:check + +or + +mvn dependency-check:aggregate +``` + ###### Run Personal Dev Script To build and run CWS, use your i.e.:`jsmith.sh` script - its usage is as follows: @@ -177,8 +186,8 @@ The CWS image available on Github is loaded with self-signed SSL certs that requ If you'd like to provide your own SSL certs, you can use the `generate_certs.sh` script in `cws_certs/` to do so. You'll then need to copy those files into the image before startup or (more easily) use volume mounts to make them available to CWS. Take a look at the `docker-compose.yml` file in `install/docker/` -- there are commented-out volume store lines that you can use. The keystore files CWS looks for are at these paths inside the image: -`/home/cws_user/cws/server/apache-tomcat-9.0.75/conf/.keystore` -`/home/cws_user/cws/server/apache-tomcat-9.0.75/lib/cws_truststore.jks` +`/home/cws_user/cws/server/apache-tomcat-10.1.36/conf/.keystore` +`/home/cws_user/cws/server/apache-tomcat-10.1.36/lib/cws_truststore.jks` You'll also want to provide CWS with the password you used to create the certs so the software can use them. This is a plaintext file with the password in it. CWS looks for this file at the path: `/root/.cws/creds:ro`. Note that this password is not related to what you'd use to log into the CWS interface -- it's only the password for the certs themselves. diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..a58eaf77 --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +TODO List: + +- Add an AI component that verifies from a previous "baseline" run logs (artemis.log, cws.log, catalina.out, etc) and compares that to the newest run and discribes the differences. This can help track down any potential new errors introduced. Note for it to also find anything that is missing from the baseline logs. +- Enable the OSS Index analyzer in the Dependency-Check to catch more potential security risks + - Requires to authenticate (create a new account on ossindex.sonatype.org) +- Update Elasticsearch and logstash to the newest version +- Add one integration test to the docker.yml Github Actions Workflow, just to test the basics in in the docker version + - Name it like DockerTestIT diff --git a/ci/ci.sh b/ci/ci.sh index fb0ed3a5..7b05e8bc 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -230,7 +230,7 @@ ${DIST}/console-only/cws/start_cws.sh -d $BASE_PORT # AMQ must be started before configuring workers print "Waiting for console startup..." -sleep 120 +sleep 60 # ------------------------- # CONFIGURE & START WORKERS diff --git a/create_server_dist.sh b/create_server_dist.sh index 6c5f2f89..a4de4cc3 100755 --- a/create_server_dist.sh +++ b/create_server_dist.sh @@ -21,9 +21,11 @@ rm -rf ${DIST} print 'Creating new CWS distribution directory...' mkdir -p ${CWS}/{bpmn,config/templates,installer,logs,upgrade,sql/cws} +${ROOT}/install/create_camunda_zips.sh $CAMUNDA_VER + print 'Unzipping Camunda into distribution...' -unzip ${INSTALL_DIR}/cws_camunda-bpm-tomcat-${CAMUNDA_VER}.zip -x start-camunda.bat start-camunda.sh -d ${CWS} > ${CWS}/logs/camunda_extract_main.log 2>&1 -unzip ${INSTALL_DIR}/cws_camunda-bpm-tomcat-${CAMUNDA_VER}-lib.zip -d ${CWS}/lib > ${CWS}/logs/camunda_extract_lib.log 2>&1 +unzip ${INSTALL_DIR}/camunda-distro-zips/cws_camunda-bpm-ee-tomcat-${CAMUNDA_VER}.zip -x start-camunda.bat start-camunda.sh -d ${CWS} > ${CWS}/logs/camunda_extract_main.log 2>&1 +unzip ${INSTALL_DIR}/camunda-distro-zips/cws_camunda-bpm-ee-tomcat-${CAMUNDA_VER}-lib.zip -d ${CWS}/lib > ${CWS}/logs/camunda_extract_lib.log 2>&1 if [[ $? -gt 0 ]]; then print "ERROR: failed to unzip Camunda distribution, check ${CWS}/logs/camunda_extract.log for details." @@ -78,6 +80,7 @@ cp ${INSTALL_DIR}/cws-engine/process_start_req_listener.xml ${CONFIG_TEMPLATES cp ${INSTALL_DIR}/cws-engine/cws-engine.properties ${CONFIG_TEMPLATES_DIR}/cws-engine cp ${INSTALL_DIR}/cws-ui/cws-ui.properties ${CONFIG_TEMPLATES_DIR}/cws-ui cp ${INSTALL_DIR}/cws-ui/applicationContext.xml ${CONFIG_TEMPLATES_DIR}/cws-ui +cp ${INSTALL_DIR}/cws-ui/broker.xml ${CONFIG_TEMPLATES_DIR}/cws-ui cp ${INSTALL_DIR}/cws-ui/*.ftl ${CONFIG_TEMPLATES_DIR}/cws-ui cp ${INSTALL_DIR}/cws-ui/sqs_dispatcher_thread_bean.xml ${CONFIG_TEMPLATES_DIR}/cws-ui cp ${INSTALL_DIR}/camunda_mods/web.xml ${CONFIG_TEMPLATES_DIR}/camunda_mods @@ -111,7 +114,7 @@ cp ${INSTALL_DIR}/tomcat_root/not_authenticated.html ${CWS_TOMCAT_ROOT}/webapps/ rm -rf ${CWS_TOMCAT_ROOT}/webapps/docs print 'Installing DB drivers to Tomcat...' -cp ${ROOT}/cws-installer/cws-installer-libs/mysql-connector-java-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-installer/cws-installer-libs/mysql-connector-j-*.jar ${TOMCAT_LIB_DIR} cp ${ROOT}/cws-installer/cws-installer-libs/mariadb-java-client-*.jar ${TOMCAT_LIB_DIR} cp ${ROOT}/cws-installer/cws-installer-libs/HikariCP-*.jar ${TOMCAT_LIB_DIR} @@ -119,27 +122,35 @@ print 'Installing core libraries to Tomcat...' cp ${ROOT}/cws-core/target/cws-core.jar ${TOMCAT_LIB_DIR} rm -f ${TOMCAT_LIB_DIR}/slf4j*.jar +rm -f ${TOMCAT_LIB_DIR}/commons-logging*.jar cp ${ROOT}/cws-core/cws-core-libs/slf4j-api-*.jar ${TOMCAT_LIB_DIR} cp ${ROOT}/cws-core/cws-core-libs/log4j-slf4j-impl*.jar ${TOMCAT_LIB_DIR} cp ${ROOT}/cws-core/cws-core-libs/log4j-*.jar ${TOMCAT_LIB_DIR} cp ${ROOT}/cws-core/cws-core-libs/jython*.jar ${TOMCAT_LIB_DIR} +print 'Installing Jakarta Mail libraries to Tomcat...' +cp ${ROOT}/cws-core/cws-core-libs/jakarta.mail-api-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-core/cws-core-libs/jakarta.mail-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-core/cws-core-libs/angus-activation-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-core/cws-core-libs/jakarta.activation-api-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-core/cws-core-libs/commons-email2-jakarta-*.jar ${TOMCAT_LIB_DIR} + print 'Installing cws-tasks libraries to Tomcat...' -cp ${ROOT}/cws-tasks/cws-tasks-libs/commons-configuration-*.jar ${TOMCAT_LIB_DIR} +cp ${ROOT}/cws-tasks/cws-tasks-libs/commons-configuration2-*.jar ${TOMCAT_LIB_DIR} print 'Installing cws-ui libraries to Tomcat...' CWS_CONSOLE_WEBAPP=${CWS_TOMCAT_ROOT}/webapps/cws-ui cp ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/commons-io-*.jar ${TOMCAT_LIB_DIR} -cp ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/commons-lang-*.jar ${TOMCAT_LIB_DIR} -cp ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/commons-logging-*.jar ${TOMCAT_LIB_DIR} +cp ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/commons-lang3-*.jar ${TOMCAT_LIB_DIR} +# Note: commons-logging is not copied as CWS uses Log4j2 with SLF4J bindings +# cp ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/commons-logging-*.jar ${TOMCAT_LIB_DIR} print 'Removing slf4j lib from cws-ui...' rm ${CWS_CONSOLE_WEBAPP}/WEB-INF/lib/slf4j*.jar print 'Installing cws-engine libraries to Tomcat...' CWS_ENGINE_WEBAPP=${CWS_TOMCAT_ROOT}/webapps/cws-engine -cp ${CWS_ENGINE_WEBAPP}/WEB-INF/lib/jersey-guava-*.jar ${TOMCAT_LIB_DIR} cp ${CWS_ENGINE_WEBAPP}/WEB-INF/lib/gson-*.jar ${TOMCAT_LIB_DIR} print "Modifying Camunda webapp..." diff --git a/cws-adaptation-engine/pom.xml b/cws-adaptation-engine/pom.xml index 032660b9..01b1ea65 100644 --- a/cws-adaptation-engine/pom.xml +++ b/cws-adaptation-engine/pom.xml @@ -41,22 +41,17 @@ - javax.servlet - javax.servlet-api - - - - javax.jms - jms + jakarta.servlet + jakarta.servlet-api + - javax.jms - javax.jms-api + jakarta.jms + jakarta.jms-api - javax.activation - activation - 1.1.1 + jakarta.activation + jakarta.activation-api @@ -64,16 +59,19 @@ junit - org.mockito - mockito-core + com.icegreen + greenmail + test - dumbster - dumbster + org.eclipse.jetty + jetty-server + test - org.glassfish.grizzly - grizzly-http-server + org.eclipse.jetty.ee10 + jetty-ee10-servlet + test @@ -87,8 +85,8 @@ mariadb-java-client - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -103,17 +101,9 @@ spring-context - - org.apache.activemq - activemq-client + artemis-jakarta-client org.slf4j @@ -121,20 +111,15 @@ - - - de.ruedigermoeller - fst - - + org.apache.commons - commons-email + commons-email2-jakarta - javax.mail - mail + jakarta.mail + jakarta.mail-api @@ -153,9 +138,9 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true diff --git a/cws-adaptation/pom.xml b/cws-adaptation/pom.xml index 28ebf3f0..4e4fbce8 100644 --- a/cws-adaptation/pom.xml +++ b/cws-adaptation/pom.xml @@ -12,23 +12,42 @@ gov.nasa.jpl.ammos.ids.cws cws-service + + gov.nasa.jpl.ammos.ids.cws + cws-core + org.apache.logging.log4j log4j-slf4j-impl provided + + org.slf4j + slf4j-api + junit junit - javax.servlet - javax.servlet-api - provided + jakarta.servlet + jakarta.servlet-api + + + org.springframework + spring-web + + + org.springframework + spring-context - + + io.swagger.core.v3 + swagger-annotations-jakarta + + org.apache.commons commons-exec @@ -49,15 +68,14 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true org.apache.maven.plugins maven-jar-plugin - 3.1.0 **/CustomMethods* @@ -66,5 +84,4 @@ - diff --git a/cws-adaptation/src/main/java/jpl/cws/controller/custom/MyRestService.java b/cws-adaptation/src/main/java/jpl/cws/controller/custom/MyRestService.java index 816825c3..69064dc8 100644 --- a/cws-adaptation/src/main/java/jpl/cws/controller/custom/MyRestService.java +++ b/cws-adaptation/src/main/java/jpl/cws/controller/custom/MyRestService.java @@ -3,14 +3,14 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; -import springfox.documentation.annotations.ApiIgnore; +import io.swagger.v3.oas.annotations.Hidden; @Controller @RequestMapping("/api") @@ -19,7 +19,7 @@ public class MyRestService { public MyRestService() { - System.out.println("MyRestService xtor"); + log.debug("MyRestService xtor"); } @@ -28,7 +28,7 @@ public MyRestService() { * Example POST service * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/example", method = POST) public @ResponseBody String example( final HttpSession session) { diff --git a/cws-core/pom.xml b/cws-core/pom.xml index 535f4cc1..56bf4cbc 100644 --- a/cws-core/pom.xml +++ b/cws-core/pom.xml @@ -13,10 +13,19 @@ + + org.apache.logging.log4j + log4j-core + + + + org.slf4j + slf4j-api + + org.apache.logging.log4j log4j-slf4j-impl - provided @@ -26,8 +35,9 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api + provided @@ -41,6 +51,7 @@ org.camunda.bpm.identity camunda-identity-ldap + provided * @@ -85,23 +96,79 @@ org.springframework spring-webmvc + + + commons-logging + commons-logging + + org.springframework - spring-test + spring-context + + + commons-logging + commons-logging + + + + + org.springframework + spring-tx + + + commons-logging + commons-logging + + + + + org.springframework + spring-beans + + + commons-logging + commons-logging + + org.springframework spring-jms + + + commons-logging + commons-logging + + org.springframework spring-jdbc + + + commons-logging + commons-logging + + + + + org.springframework + spring-test + test + + + commons-logging + commons-logging + + org.camunda.bpm camunda-bpm-assert + test * @@ -112,11 +179,17 @@ org.assertj assertj-core + test - commons-lang - commons-lang + org.apache.commons + commons-lang3 + + + + org.apache.commons + commons-text commons-io @@ -126,29 +199,32 @@ org.python jython-standalone - - - de.ruedigermoeller - fst - - + com.google.code.gson gson - + + - javax.jms - jms + com.fasterxml.jackson.core + jackson-core - javax.jms - javax.jms-api + com.fasterxml.jackson.core + jackson-databind + + + + jakarta.jms + jakarta.jms-api + provided junit junit + test org.jacoco @@ -157,25 +233,51 @@ org.apache.commons - commons-email + commons-email2-jakarta + + + jakarta.mail + jakarta.mail-api + + + org.eclipse.angus + jakarta.mail + + + jakarta.activation + jakarta.activation-api + + + commons-fileupload + commons-fileupload + + + org.springframework + spring-web + + + commons-logging + commons-logging + + - - + ${project.artifactId} maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + + -parameters + org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy-dependencies @@ -194,7 +296,5 @@ - - diff --git a/cws-core/src/main/java/jpl/cws/core/ProcDefUtils.java b/cws-core/src/main/java/jpl/cws/core/ProcDefUtils.java index 61bdc94e..4ed3e6c4 100644 --- a/cws-core/src/main/java/jpl/cws/core/ProcDefUtils.java +++ b/cws-core/src/main/java/jpl/cws/core/ProcDefUtils.java @@ -17,11 +17,14 @@ public class ProcDefUtils { private Object cwsBean; public ProcDefUtils() { - cwsBean = SpringApplicationContext.getBean("cws"); - log.trace("CWS Bean = " + cwsBean); + if (SpringApplicationContext.isContextAvailable()) { + cwsBean = SpringApplicationContext.getBean("cws"); + log.trace("CWS Bean = " + cwsBean); + } else { + log.warn("Spring ApplicationContext not available during ProcDefUtils construction. Bean will be retrieved later if needed."); + } } - public String getClasspathUrls() { ClassLoader thisCl = this.getClass().getClassLoader(); System.out.println("CL = " + thisCl); @@ -53,6 +56,15 @@ public String getClasspathUrls() { * */ public Object callSnippet(String methodName, Object ... params) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // Get cwsBean if not already available + if (cwsBean == null) { + if (SpringApplicationContext.isContextAvailable()) { + cwsBean = SpringApplicationContext.getBean("cws"); + } else { + throw new IllegalStateException("Cannot call snippet '" + methodName + "': Spring ApplicationContext is not available and cwsBean is null."); + } + } + log.debug("about to call method : " + methodName); Class>[] methodParamTypes = new Class>[params.length]; int i = 0; @@ -63,5 +75,4 @@ public Object callSnippet(String methodName, Object ... params) throws NoSuchMet log.debug("got method: " + method); return method.invoke(cwsBean, params); } - } diff --git a/cws-core/src/main/java/jpl/cws/core/Utils.java b/cws-core/src/main/java/jpl/cws/core/Utils.java index cd42e658..0fdffb5e 100644 --- a/cws-core/src/main/java/jpl/cws/core/Utils.java +++ b/cws-core/src/main/java/jpl/cws/core/Utils.java @@ -8,18 +8,26 @@ public class Utils { private static final Logger log = LoggerFactory.getLogger(Utils.class); - private Object cwsBean; private CwsConfig cwsConfig; public Utils() { - cwsBean = SpringApplicationContext.getBean("cws"); - log.debug("cwsBean = " + cwsBean); - - cwsConfig = (CwsConfig) SpringApplicationContext.getBean("cwsConfig"); - log.debug("cwsConfig = " + cwsConfig); + if (SpringApplicationContext.isContextAvailable()) { + cwsConfig = (CwsConfig)SpringApplicationContext.getBean("cwsConfig"); + log.debug("cwsConfig = " + cwsConfig); + } else { + log.warn("Spring ApplicationContext not available during Utils construction. cwsConfig will be retrieved later if needed."); + } } public String getCwsVar(String name) throws Exception { + // Get cwsConfig if not already available + if (cwsConfig == null) { + if (SpringApplicationContext.isContextAvailable()) { + cwsConfig = (CwsConfig)SpringApplicationContext.getBean("cwsConfig"); + } else { + throw new IllegalStateException("Cannot get CWS variable '" + name + "': Spring ApplicationContext is not available and CwsConfig is null."); + } + } if (name.equals("hostname")) { return cwsConfig.getInstallHostname(); } else if (name.equals("installDir")) { return cwsConfig.getInstallDir(); } diff --git a/cws-core/src/main/java/jpl/cws/core/db/SchedulerDbService.java b/cws-core/src/main/java/jpl/cws/core/db/SchedulerDbService.java index 2457ffb4..6318f850 100644 --- a/cws-core/src/main/java/jpl/cws/core/db/SchedulerDbService.java +++ b/cws-core/src/main/java/jpl/cws/core/db/SchedulerDbService.java @@ -1,9 +1,10 @@ package jpl.cws.core.db; -import de.ruedigermoeller.serialization.FSTObjectOutput; +import com.fasterxml.jackson.databind.ObjectMapper; import jpl.cws.core.log.CwsEmailerService; -import org.apache.commons.lang.StringUtils; -import org.joda.time.DateTime; +import org.apache.commons.lang3.StringUtils; +import java.time.Instant; +import java.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -15,15 +16,13 @@ import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.jdbc.support.lob.LobCreator; -import java.io.ByteArrayOutputStream; -import java.sql.Array; +import java.nio.charset.StandardCharsets; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; -import java.util.*; - import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.*; /** @@ -97,7 +96,7 @@ public void markProcessInstanceComplete(String uuid) { "error_message=? " + "WHERE uuid=? AND status != ? AND status != ?", new Object[]{COMPLETE, - new Timestamp(DateTime.now().getMillis()), + Timestamp.from(Instant.now()), null, uuid, COMPLETE, FAIL}); if (numUpdated == 0) { @@ -172,41 +171,40 @@ public boolean engineProcessRowExists(String procDefKey) { public void insertSchedEngineProcInstRow(final SchedulerJob schedulerJob) throws Exception { long t0 = System.currentTimeMillis(); - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - FSTObjectOutput out = new FSTObjectOutput(os); - out.writeObject(schedulerJob.getProcVariables()); - out.close(); - - DefaultLobHandler lobHandler = new DefaultLobHandler(); - Object o = jdbcTemplate.execute( - INSERT_SCHED_WORKER_PROC_INST_ROW_SQL, - new AbstractLobCreatingPreparedStatementCallback(lobHandler) { - protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException { - ps.setString(1, schedulerJob.getUuid()); - ps.setTimestamp(2, schedulerJob.getCreatedTime()); // will get auto-filled by DB - ps.setTimestamp(3, schedulerJob.getUpdatedTime()); // will get auto-filled by DB - ps.setString(4, null); // don't know process instance ID yet - ps.setString(5, schedulerJob.getProcDefKey()); - ps.setString(6, schedulerJob.getProcBusinessKey()); - ps.setInt(7, schedulerJob.getProcPriority()); - lobCreator.setBlobAsBytes(ps, 8, os.toByteArray()); - ps.setString(9, schedulerJob.getStatus()); - ps.setString(10, null); // no error message yet - ps.setString(11, schedulerJob.getInitiationKey()); - ps.setString(12, null); // no claimed_by_worker yet - ps.setString(13, null); // not started on any worker yet - ps.setString(14, null); // no worker rejections yet - ps.setInt(15, 0); // no worker attempts yet - ps.setString(16, null); // no claim_uuid yet - } + // Serialize proc variables as JSON (UTF-8) for safer, portable storage + ObjectMapper objectMapper = new ObjectMapper(); + String json = objectMapper.writeValueAsString(schedulerJob.getProcVariables()); + byte[] procVariablesBytes = json.getBytes(StandardCharsets.UTF_8); + + DefaultLobHandler lobHandler = new DefaultLobHandler(); + Object o = jdbcTemplate.execute( + INSERT_SCHED_WORKER_PROC_INST_ROW_SQL, + new AbstractLobCreatingPreparedStatementCallback(lobHandler) { + protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException { + ps.setString(1, schedulerJob.getUuid()); + ps.setTimestamp(2, schedulerJob.getCreatedTime()); // will get auto-filled by DB + ps.setTimestamp(3, schedulerJob.getUpdatedTime()); // will get auto-filled by DB + ps.setString(4, null); // don't know process instance ID yet + ps.setString(5, schedulerJob.getProcDefKey()); + ps.setString(6, schedulerJob.getProcBusinessKey()); + ps.setInt(7, schedulerJob.getProcPriority()); + lobCreator.setBlobAsBytes(ps, 8, procVariablesBytes); + ps.setString(9, schedulerJob.getStatus()); + ps.setString(10, null); // no error message yet + ps.setString(11, schedulerJob.getInitiationKey()); + ps.setString(12, null); // no claimed_by_worker yet + ps.setString(13, null); // not started on any worker yet + ps.setString(14, null); // no worker rejections yet + ps.setInt(15, 0); // no worker attempts yet + ps.setString(16, null); // no claim_uuid yet } - ); - log.trace("RETURN OBJECT: " + o); - o = null; - long timeTaken = System.currentTimeMillis() - t0; - if (timeTaken > SLOW_WARN_THRESHOLD) { - log.warn("INSERT INTO cws_sched_worker_proc_inst took " + timeTaken + " ms!"); - } + } + ); + log.trace("RETURN OBJECT: " + o); + o = null; + long timeTaken = System.currentTimeMillis() - t0; + if (timeTaken > SLOW_WARN_THRESHOLD) { + log.warn("INSERT INTO cws_sched_worker_proc_inst took " + timeTaken + " ms!"); } } @@ -251,7 +249,7 @@ public void updateProcInstRowStatus( "error_message=? " + "WHERE uuid=? AND status=?", new Object[]{newStatus, - new Timestamp(DateTime.now().getMillis()), + Timestamp.from(Instant.now()), errorMessage, uuid, oldStatus}); if (numUpdated == 0 && ++numTries < 20) { String rowStatus = getProcInstRowStatus(uuid); @@ -296,7 +294,7 @@ public int updateProcInstIdAndStartedByWorker( new Object[]{ workerId, procInstId, - new Timestamp(DateTime.now().getMillis()), + Timestamp.from(Instant.now()), uuid} ); long timeTaken = System.currentTimeMillis() - t0; @@ -547,7 +545,7 @@ public String createExternalWorkerRow(String workerId, String hostname) { int numTries = 0; String workerName = null; while (numTries++ < 10 && numUpdated != 1) { - Timestamp tsNow = new Timestamp(DateTime.now().getMillis()); + Timestamp tsNow = Timestamp.from(Instant.now()); workerName = "ext_worker" + String.format("%1$4s", externalWorkerNum++).replace(' ', '0'); try { @@ -589,7 +587,7 @@ public String createExternalWorkerRow(String workerId, String hostname) { public int updateExternalWorkerHeartbeat(String workerId) { return jdbcTemplate.update( "UPDATE cws_external_worker SET last_heartbeat_time = ? WHERE id=?", - new Object[]{new Timestamp(DateTime.now().getMillis()), workerId} + new Object[]{Timestamp.from(Instant.now()), workerId} ); } @@ -696,7 +694,7 @@ public List> getWorkerNumRunningProcs() { */ public List> detectDeadWorkers(int thresholdMilliseconds) { try { - Timestamp thresholdTimeAgo = new Timestamp(DateTime.now().minusMillis(thresholdMilliseconds).getMillis()); + Timestamp thresholdTimeAgo = Timestamp.from(Instant.now().minusMillis(thresholdMilliseconds)); return jdbcTemplate.queryForList("SELECT * FROM cws_worker " + "WHERE last_heartbeat_time < ? AND status = 'up'", new Object[]{thresholdTimeAgo}); @@ -714,7 +712,7 @@ public List> detectDeadWorkers(int thresholdMilliseconds) { */ public List> detectAbandonedWorkers(int daysToAbandoned) { try { - Timestamp thresholdTimeAgo = new Timestamp(DateTime.now().minusDays(daysToAbandoned).getMillis()); + Timestamp thresholdTimeAgo = Timestamp.from(Instant.now().minus(Duration.ofDays(daysToAbandoned))); String query = "SELECT * FROM cws_worker WHERE last_heartbeat_time < ? AND status = 'down'"; return jdbcTemplate.queryForList(query, new Object[]{thresholdTimeAgo}); @@ -732,7 +730,7 @@ public List> detectAbandonedWorkers(int daysToAbandoned) { */ public List> detectDeadExternalWorkers(int thresholdMilliseconds) { try { - Timestamp thresholdTimeAgo = new Timestamp(DateTime.now().minusMillis(thresholdMilliseconds).getMillis()); + Timestamp thresholdTimeAgo = Timestamp.from(Instant.now().minusMillis(thresholdMilliseconds)); return jdbcTemplate.queryForList("SELECT * FROM cws_external_worker " + "WHERE last_heartbeat_time < ?", new Object[]{thresholdTimeAgo}); @@ -1250,7 +1248,7 @@ public List> getProcessInstanceStats(String lastNumHours) { Timestamp time = new Timestamp(0L); if (lastNumHours != null) { - time = new Timestamp(DateTime.now().minusHours(Integer.parseInt(lastNumHours)).getMillis()); + time = Timestamp.from(Instant.now().minus(Duration.ofHours(Integer.parseInt(lastNumHours)))); } String query = @@ -1466,7 +1464,7 @@ public void updateWorkerTag(String workerId, String name, String value) throws E "WHERE worker_id=? AND name=?", new Object[]{ value, - new Timestamp(DateTime.now().getMillis()), + Timestamp.from(Instant.now()), workerId, name}); log.debug("Updated " + numUpdated + " row(s) in the cws_worker_tags table..."); diff --git a/cws-core/src/main/java/jpl/cws/core/jms/CwsCachingConnectionFactory.java b/cws-core/src/main/java/jpl/cws/core/jms/CwsCachingConnectionFactory.java new file mode 100644 index 00000000..08193476 --- /dev/null +++ b/cws-core/src/main/java/jpl/cws/core/jms/CwsCachingConnectionFactory.java @@ -0,0 +1,70 @@ +package jpl.cws.core.jms; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jms.connection.CachingConnectionFactory; +import jakarta.jms.ConnectionFactory; +import jpl.cws.core.util.NettyShutdownUtil; + +/** + * Custom CachingConnectionFactory that provides better shutdown handling + * for ActiveMQ Artemis to prevent NoClassDefFoundError during application shutdown. + * + * This factory adds a delay during destruction to allow Netty threads + * to terminate gracefully before the web application classloader is destroyed. + */ +public class CwsCachingConnectionFactory extends CachingConnectionFactory { + + private static final Logger log = LoggerFactory.getLogger(CwsCachingConnectionFactory.class); + private static final long SHUTDOWN_DELAY_MS = 5000; // 5 seconds delay + + public CwsCachingConnectionFactory() { + super(); + } + + public CwsCachingConnectionFactory(ConnectionFactory targetConnectionFactory) { + super(targetConnectionFactory); + } + + public CwsCachingConnectionFactory(ConnectionFactory targetConnectionFactory, int sessionCacheSize) { + super(targetConnectionFactory); + setSessionCacheSize(sessionCacheSize); + } + + @Override + public void destroy() { + log.info("Starting destruction..."); + + try { + // First, call the parent destroy method + super.destroy(); + log.info("Parent destroy completed"); + + // Shutdown MySQL connection cleanup threads + NettyShutdownUtil.shutdownMysqlConnectionCleanup(); + + // Add a delay to allow Netty threads to terminate gracefully + log.info("Waiting {}ms for Netty threads to terminate...", SHUTDOWN_DELAY_MS); + Thread.sleep(SHUTDOWN_DELAY_MS); + + log.info("Destruction completed successfully"); + + } catch (InterruptedException e) { + log.warn("Destruction interrupted: {}", e.getMessage()); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error("Error during destruction: {}", e.getMessage(), e); + } + } + + @Override + public void resetConnection() { + log.info("Resetting connection..."); + try { + super.resetConnection(); + log.info("Connection reset completed"); + } catch (Exception e) { + log.error("Error during connection reset: {}", e.getMessage(), e); + } + } +} diff --git a/cws-core/src/main/java/jpl/cws/core/log/CwsEmailerService.java b/cws-core/src/main/java/jpl/cws/core/log/CwsEmailerService.java index 03aab6ef..228a6bfe 100644 --- a/cws-core/src/main/java/jpl/cws/core/log/CwsEmailerService.java +++ b/cws-core/src/main/java/jpl/cws/core/log/CwsEmailerService.java @@ -1,7 +1,7 @@ package jpl.cws.core.log; -import org.apache.commons.mail.Email; -import org.apache.commons.mail.HtmlEmail; +import org.apache.commons.mail2.jakarta.Email; +import org.apache.commons.mail2.jakarta.HtmlEmail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-core/src/main/java/jpl/cws/core/log/CwsWorkerLoggerFactory.java b/cws-core/src/main/java/jpl/cws/core/log/CwsWorkerLoggerFactory.java index 5e89583a..a8653348 100644 --- a/cws-core/src/main/java/jpl/cws/core/log/CwsWorkerLoggerFactory.java +++ b/cws-core/src/main/java/jpl/cws/core/log/CwsWorkerLoggerFactory.java @@ -13,7 +13,6 @@ public class CwsWorkerLoggerFactory { @Autowired private CwsConfig cwsConfig; public CwsWorkerLoggerFactory() { - System.out.println("CwsWorkerLoggerFactory constructor... cwsConfig=" + cwsConfig); } public CwsWorkerLogger getLogger(Class clazz) { diff --git a/cws-core/src/main/java/jpl/cws/core/service/ProcessService.java b/cws-core/src/main/java/jpl/cws/core/service/ProcessService.java index 04c9c1fb..28a61523 100644 --- a/cws-core/src/main/java/jpl/cws/core/service/ProcessService.java +++ b/cws-core/src/main/java/jpl/cws/core/service/ProcessService.java @@ -4,12 +4,12 @@ import java.util.List; import java.util.Map; -import javax.jms.BytesMessage; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; -import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.camunda.bpm.engine.RepositoryService; import org.camunda.bpm.engine.repository.ProcessDefinition; import org.slf4j.Logger; @@ -121,7 +121,7 @@ public Map getProcInstStatusMap(Map cwsProcInstRow } else { log.warn("no Camunda knowledge of procInstId: " + procInstId); - log.warn(ExceptionUtils.getFullStackTrace(new Throwable())); + log.warn(ExceptionUtils.getStackTrace(new Throwable())); } } else { diff --git a/cws-core/src/main/java/jpl/cws/core/service/SecurityService.java b/cws-core/src/main/java/jpl/cws/core/service/SecurityService.java index 437f89fc..0d614c9b 100644 --- a/cws-core/src/main/java/jpl/cws/core/service/SecurityService.java +++ b/cws-core/src/main/java/jpl/cws/core/service/SecurityService.java @@ -6,7 +6,8 @@ import java.util.Map; import org.camunda.bpm.engine.IdentityService; -import org.joda.time.DateTime; +import java.time.Instant; +import java.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -121,7 +122,7 @@ public CwsToken getCwsToken(String cwsToken, String username) { */ public void addNewCwsTokenToDb(String cwsToken, String username) { log.info("adding new token to db: " + cwsToken + ", " + username); - Timestamp expirationTime = new Timestamp(DateTime.now().plusHours(prop_TokenExpirationInHours).getMillis()); + Timestamp expirationTime = Timestamp.from(Instant.now().plus(Duration.ofHours(prop_TokenExpirationInHours))); schedulerDbService.insertCwsToken(cwsToken, username, expirationTime); } diff --git a/cws-core/src/main/java/jpl/cws/core/service/SpringApplicationContext.java b/cws-core/src/main/java/jpl/cws/core/service/SpringApplicationContext.java index be0925ad..e5c8a12c 100644 --- a/cws-core/src/main/java/jpl/cws/core/service/SpringApplicationContext.java +++ b/cws-core/src/main/java/jpl/cws/core/service/SpringApplicationContext.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; /** * Useful helper class from: @@ -26,6 +27,7 @@ * we do not need a reference to the Servlet context for this. All we need is * for this bean to be initialized during application startup. */ +@Component public class SpringApplicationContext implements ApplicationContextAware, BeanFactoryPostProcessor { private static final Logger log = LoggerFactory.getLogger(SpringApplicationContext.class); private static ApplicationContext CONTEXT; @@ -44,6 +46,14 @@ public void setApplicationContext(ApplicationContext context) throws BeansExcept CONTEXT = context; } + /** + * Check if the Spring ApplicationContext is available. + * @return true if the context is available, false otherwise + */ + public static boolean isContextAvailable() { + return CONTEXT != null; + } + public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { this.factory = factory; @@ -60,11 +70,19 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) thro * @return an Object reference to the named bean. */ public static Object getBean(String beanName) { - log.trace("CWS: Getting bean '"+beanName+"' from context "+CONTEXT); + if (!isContextAvailable()) { + log.warn("CWS: Spring ApplicationContext is null, cannot get bean '" + beanName + "'. This may occur during application shutdown."); + throw new IllegalStateException("Spring ApplicationContext is null. Cannot get bean '" + beanName + "'. This may occur during application shutdown."); + } + log.trace("CWS: Getting bean '" + beanName + "' from context " + CONTEXT); return CONTEXT.getBean(beanName); } public static Map getBeansOfType(Class type) { + if (!isContextAvailable()) { + log.warn("CWS: Spring ApplicationContext is null, cannot get beans of type '" + type.getName() + "'. This may occur during application shutdown."); + throw new IllegalStateException("Spring ApplicationContext is null. Cannot get beans of type '" + type.getName() + "'. This may occur during application shutdown."); + } return CONTEXT.getBeansOfType(type); } @@ -118,6 +136,10 @@ public void replaceBean(String beanName, ConstructorArgumentValues constructorAr public String[] getBeanDefinitionNamesOfType(Class type) { + if (!isContextAvailable()) { + log.warn("CWS: Spring ApplicationContext is null, cannot get bean names of type '" + type.getName() + "'. This may occur during application shutdown."); + throw new IllegalStateException("Spring ApplicationContext is null. Cannot get bean names of type '" + type.getName() + "'. This may occur during application shutdown."); + } return CONTEXT.getBeanNamesForType(type); } @@ -130,6 +152,10 @@ public void removeBean(String beanName) { public void removeAllBeansOfType(Class type) { + if (!isContextAvailable()) { + log.warn("CWS: Spring ApplicationContext is null, cannot remove beans of type '" + type.getName() + "'. This may occur during application shutdown."); + throw new IllegalStateException("Spring ApplicationContext is null. Cannot remove beans of type '" + type.getName() + "'. This may occur during application shutdown."); + } BeanDefinitionRegistry registry = (BeanDefinitionRegistry) CONTEXT.getAutowireCapableBeanFactory(); for(String beanName : CONTEXT.getBeanNamesForType(type)){ log.debug("REMOVING BEAN (of type " + type + "): " + beanName); diff --git a/cws-core/src/main/java/jpl/cws/core/util/NettyShutdownUtil.java b/cws-core/src/main/java/jpl/cws/core/util/NettyShutdownUtil.java new file mode 100644 index 00000000..527db463 --- /dev/null +++ b/cws-core/src/main/java/jpl/cws/core/util/NettyShutdownUtil.java @@ -0,0 +1,449 @@ +package jpl.cws.core.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +/** + * Utility class for properly shutting down Netty components to prevent + * NoClassDefFoundError during application shutdown. + * + * This is particularly important for ActiveMQ Artemis which uses Netty + * for network communication. + */ +public class NettyShutdownUtil { + + private static final Logger log = LoggerFactory.getLogger(NettyShutdownUtil.class); + private static final long SHUTDOWN_TIMEOUT_MS = 5000; + private static volatile boolean shutdownHookRegistered = false; + + /** + * Registers a JVM shutdown hook to ensure Netty threads are properly cleaned up + * even if the servlet context listener doesn't get called. + */ + public static synchronized void registerShutdownHook() { + if (!shutdownHookRegistered) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("JVM shutdown hook triggered"); + shutdownMysqlConnectionCleanup(); + forceShutdownNettyThreads(); + })); + shutdownHookRegistered = true; + log.info("Shutdown hook registered"); + } + } + + /** + * Forces shutdown of all Netty-related threads and cleans up ThreadLocal resources. + * This method is safe to call multiple times. + */ + public static void forceShutdownNettyThreads() { + log.info("Starting forced Netty thread shutdown..."); + + try { + // First, try to clean up Netty ThreadLocal resources + cleanupNettyThreadLocals(); + + // Then try to interrupt all Netty threads + Thread.getAllStackTraces().keySet().stream() + .filter(t -> isNettyThread(t)) + .forEach(thread -> { + log.debug("Interrupting thread: {}", thread.getName()); + thread.interrupt(); + }); + + // Give threads time to terminate gracefully + Thread.sleep(1000); + + // Check if any Netty threads are still alive + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < SHUTDOWN_TIMEOUT_MS) { + boolean hasNettyThreads = Thread.getAllStackTraces().keySet().stream() + .anyMatch(t -> isNettyThread(t) && t.isAlive()); + + if (!hasNettyThreads) { + log.info("All Netty threads terminated gracefully"); + return; + } + + Thread.sleep(100); + } + + // Force stop any remaining Netty threads using safer method + log.warn("Force stopping remaining Netty threads..."); + Thread.getAllStackTraces().keySet().stream() + .filter(t -> isNettyThread(t) && t.isAlive()) + .forEach(thread -> { + log.warn("Force stopping thread: {}", thread.getName()); + try { + // Use interrupt instead of deprecated stop() method + thread.interrupt(); + // Give thread a moment to respond to interrupt + Thread.sleep(100); + if (thread.isAlive()) { + log.warn("Thread {} did not respond to interrupt, marking as daemon", thread.getName()); + // Mark as daemon so it doesn't prevent JVM shutdown + thread.setDaemon(true); + } + } catch (Exception e) { + log.error("Error force stopping thread {}: {}", thread.getName(), e.getMessage()); + } + }); + + } catch (Exception e) { + log.error("Error during Netty thread shutdown: {}", e.getMessage(), e); + } + + log.info("Netty thread shutdown completed"); + } + + /** + * Determines if a thread is a Netty-related thread. + */ + private static boolean isNettyThread(Thread thread) { + String name = thread.getName().toLowerCase(); + return name.contains("netty") || + name.contains("activemq-client-netty-threads") || + name.contains("artemis") || + name.contains("nioeventloop") || + name.contains("eventloop") || + name.contains("activemq-pageexecutor"); + } + + /** + * Attempts to clean up Netty ThreadLocal resources to prevent memory leaks. + */ + private static void cleanupNettyThreadLocals() { + try { + log.debug("Attempting to cleanup Netty ThreadLocal resources..."); + + // Try to access Netty's InternalThreadLocalMap and clean it up + try { + Class> internalThreadLocalMapClass = Class.forName("io.netty.util.internal.InternalThreadLocalMap"); + Method getMethod = internalThreadLocalMapClass.getMethod("get"); + Method removeMethod = internalThreadLocalMapClass.getMethod("remove"); + + // Get the current thread's InternalThreadLocalMap + Object threadLocalMap = getMethod.invoke(null); + if (threadLocalMap != null) { + log.debug("Found InternalThreadLocalMap, attempting cleanup..."); + removeMethod.invoke(null); + log.debug("InternalThreadLocalMap cleanup completed"); + } + } catch (Exception e) { + log.debug("Could not access InternalThreadLocalMap: {}", e.getMessage()); + } + + // Try to clean up FastThreadLocal resources + try { + Class> fastThreadLocalClass = Class.forName("io.netty.util.concurrent.FastThreadLocal"); + Method removeAllMethod = fastThreadLocalClass.getMethod("removeAll"); + removeAllMethod.invoke(null); + log.debug("FastThreadLocal cleanup completed"); + } catch (Exception e) { + log.debug("Could not access FastThreadLocal: {}", e.getMessage()); + } + + } catch (Exception e) { + log.error("Error during ThreadLocal cleanup: {}", e.getMessage(), e); + } + } + + /** + * Shuts down MySQL connection cleanup threads to prevent memory leaks. + */ + public static void shutdownMysqlConnectionCleanup() { + try { + log.info("Attempting to shutdown MySQL connection cleanup threads..."); + + // Try to access MySQL's AbandonedConnectionCleanupThread + try { + Class> cleanupThreadClass = Class.forName("com.mysql.cj.jdbc.AbandonedConnectionCleanupThread"); + Method checkedShutdownMethod = cleanupThreadClass.getMethod("checkedShutdown"); + checkedShutdownMethod.invoke(null); + log.info("MySQL connection cleanup thread shutdown completed"); + } catch (Exception e) { + log.debug("Could not shutdown MySQL cleanup thread: {}", e.getMessage()); + } + + // Also try to interrupt any MySQL-related threads + Thread.getAllStackTraces().keySet().stream() + .filter(t -> t.getName().toLowerCase().contains("mysql") || + t.getName().toLowerCase().contains("connection-cleanup")) + .forEach(thread -> { + log.debug("Interrupting MySQL thread: {}", thread.getName()); + thread.interrupt(); + }); + + } catch (Exception e) { + log.error("Error during MySQL cleanup: {}", e.getMessage(), e); + } + } + + /** + * Attempts to gracefully shutdown Netty event loop groups using reflection. + * This method tries to access Netty components through various objects. + */ + public static void shutdownNettyEventLoopGroups(Object target) { + if (target == null) { + return; + } + + try { + log.info("Attempting to shutdown Netty event loop groups..."); + + // Look for common Netty event loop group field names + String[] fieldNames = { + "bossGroup", "workerGroup", "eventLoopGroup", "nioEventLoopGroup", + "clientEventLoopGroup", "serverEventLoopGroup", "acceptorEventLoopGroup" + }; + + for (String fieldName : fieldNames) { + try { + Field field = findField(target.getClass(), fieldName); + if (field != null) { + field.setAccessible(true); + Object eventLoopGroup = field.get(target); + + if (eventLoopGroup != null) { + log.debug("Found Netty event loop group: {}", fieldName); + shutdownEventLoopGroup(eventLoopGroup, fieldName); + } + } + } catch (Exception e) { + // Field doesn't exist or can't be accessed, continue to next one + } + } + + // Also try to find nested Netty components + findAndShutdownNestedNettyComponents(target); + + } catch (Exception e) { + log.error("Error during event loop group shutdown: {}", e.getMessage(), e); + } + } + + /** + * Recursively finds and shuts down nested Netty components. + */ + private static void findAndShutdownNestedNettyComponents(Object target) { + try { + Field[] fields = target.getClass().getDeclaredFields(); + for (Field field : fields) { + if (field.getName().toLowerCase().contains("netty") || + field.getName().toLowerCase().contains("eventloop") || + field.getName().toLowerCase().contains("channel")) { + + field.setAccessible(true); + Object component = field.get(target); + + if (component != null) { + log.debug("Found potential Netty component: {}", field.getName()); + shutdownEventLoopGroup(component, field.getName()); + } + } + } + } catch (Exception e) { + // Ignore errors during recursive search + } + } + + /** + * Attempts to shutdown a single event loop group. + */ + private static void shutdownEventLoopGroup(Object eventLoopGroup, String name) { + try { + // Try to call shutdownGracefully method + try { + eventLoopGroup.getClass().getMethod("shutdownGracefully").invoke(eventLoopGroup); + log.debug("Called shutdownGracefully on {}", name); + } catch (Exception e) { + // Try alternative shutdown method + try { + eventLoopGroup.getClass().getMethod("shutdown").invoke(eventLoopGroup); + log.debug("Called shutdown on {}", name); + } catch (Exception e2) { + // Try with timeout parameter + try { + eventLoopGroup.getClass().getMethod("shutdownGracefully", long.class, long.class, TimeUnit.class) + .invoke(eventLoopGroup, 100L, 1000L, TimeUnit.MILLISECONDS); + log.debug("Called shutdownGracefully with timeout on {}", name); + } catch (Exception e3) { + log.debug("Could not shutdown {}: {}", name, e3.getMessage()); + } + } + } + } catch (Exception e) { + log.error("Error shutting down {}: {}", name, e.getMessage(), e); + } + } + + /** + * Finds a field in the class hierarchy. + */ + private static Field findField(Class> clazz, String fieldName) { + while (clazz != null) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + return null; + } + + + /** + * Attempts to shutdown client-side Netty components using reflection. + * + * @param connectionFactory The ActiveMQ connection factory to shutdown Netty components from + */ + public static void shutdownClientNettyComponents(Object connectionFactory) { + try { + log.info("Attempting to shutdown client Netty components..."); + + if (connectionFactory != null) { + // Look for Netty components in the connection factory + Field[] fields = connectionFactory.getClass().getDeclaredFields(); + for (Field field : fields) { + if (field.getName().toLowerCase().contains("netty") || + field.getName().toLowerCase().contains("eventloop") || + field.getName().toLowerCase().contains("channel")) { + + field.setAccessible(true); + Object component = field.get(connectionFactory); + + if (component != null) { + log.debug("Found potential Netty component: {}", field.getName()); + + // Try to call shutdown methods + try { + component.getClass().getMethod("shutdownGracefully").invoke(component); + log.debug("Called shutdownGracefully on {}", field.getName()); + } catch (Exception e) { + try { + component.getClass().getMethod("shutdown").invoke(component); + log.debug("Called shutdown on {}", field.getName()); + } catch (Exception e2) { + // Ignore if method doesn't exist + } + } + } + } + } + + // Give components time to shutdown + Thread.sleep(500); + } + } catch (Exception e) { + log.error("Error during client Netty component shutdown: {}", e.getMessage(), e); + } + } + + /** + * Attempts to gracefully shutdown ActiveMQ Artemis thread pools to prevent IllegalMonitorStateException. + * This method tries to shutdown thread pool executors before they are forcefully terminated. + * + * @param activeMQServer The ActiveMQ Artemis server instance + */ + public static void shutdownActiveMQThreadPools(Object activeMQServer) { + if (activeMQServer == null) { + return; + } + + try { + log.info("Attempting to shutdown ActiveMQ Artemis thread pools..."); + + // Look for thread pool executors in the server + Field[] fields = activeMQServer.getClass().getDeclaredFields(); + for (Field field : fields) { + if (field.getName().toLowerCase().contains("executor") || + field.getName().toLowerCase().contains("threadpool") || + field.getName().toLowerCase().contains("pageexecutor")) { + + field.setAccessible(true); + Object executor = field.get(activeMQServer); + + if (executor != null) { + log.debug("Found potential thread pool executor: {}", field.getName()); + shutdownThreadPoolExecutor(executor, field.getName()); + } + } + } + + // Also look in nested objects + findAndShutdownThreadPools(activeMQServer); + + // Give thread pools time to shutdown gracefully + Thread.sleep(1000); + + } catch (Exception e) { + log.error("Error during ActiveMQ thread pool shutdown: {}", e.getMessage(), e); + } + } + + /** + * Recursively finds and shuts down thread pool executors. + */ + private static void findAndShutdownThreadPools(Object target) { + try { + Field[] fields = target.getClass().getDeclaredFields(); + for (Field field : fields) { + if (field.getName().toLowerCase().contains("executor") || + field.getName().toLowerCase().contains("threadpool") || + field.getName().toLowerCase().contains("pageexecutor")) { + + field.setAccessible(true); + Object executor = field.get(target); + + if (executor != null) { + log.debug("Found nested thread pool executor: {}", field.getName()); + shutdownThreadPoolExecutor(executor, field.getName()); + } + } + } + } catch (Exception e) { + // Ignore errors during recursive search + } + } + + /** + * Attempts to shutdown a single thread pool executor. + */ + private static void shutdownThreadPoolExecutor(Object executor, String name) { + try { + // Try to call shutdown method + try { + executor.getClass().getMethod("shutdown").invoke(executor); + log.debug("Called shutdown on {}", name); + + // Wait for termination with timeout + try { + Method awaitTerminationMethod = executor.getClass().getMethod("awaitTermination", long.class, TimeUnit.class); + boolean terminated = (Boolean) awaitTerminationMethod.invoke(executor, 2000L, TimeUnit.MILLISECONDS); + if (terminated) { + log.debug("Thread pool {} terminated gracefully", name); + } else { + log.warn("Thread pool {} did not terminate within timeout", name); + // Try shutdownNow as last resort + try { + executor.getClass().getMethod("shutdownNow").invoke(executor); + log.debug("Called shutdownNow on {}", name); + } catch (Exception e) { + log.debug("Could not call shutdownNow on {}: {}", name, e.getMessage()); + } + } + } catch (Exception e) { + log.debug("Could not await termination on {}: {}", name, e.getMessage()); + } + + } catch (Exception e) { + log.debug("Could not shutdown {}: {}", name, e.getMessage()); + } + } catch (Exception e) { + log.error("Error shutting down thread pool {}: {}", name, e.getMessage(), e); + } + } +} diff --git a/cws-core/src/main/java/jpl/cws/core/web/CwsCamundaSecurityFilter.java b/cws-core/src/main/java/jpl/cws/core/web/CwsCamundaSecurityFilter.java index b59b50dc..fbb7aea8 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/CwsCamundaSecurityFilter.java +++ b/cws-core/src/main/java/jpl/cws/core/web/CwsCamundaSecurityFilter.java @@ -4,15 +4,15 @@ import java.net.HttpCookie; import java.util.Enumeration; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.camunda.bpm.engine.AuthorizationService; import org.camunda.bpm.engine.IdentityService; @@ -56,7 +56,7 @@ public void init(FilterConfig filterConfig) { * all resources protected behind filter. * * (non-Javadoc) - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + * @see jakarta.servlet.Filter#doFilter(jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain) */ public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) diff --git a/cws-core/src/main/java/jpl/cws/core/web/CwsLdapSecurityFilter.java b/cws-core/src/main/java/jpl/cws/core/web/CwsLdapSecurityFilter.java index b860e39b..cd945235 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/CwsLdapSecurityFilter.java +++ b/cws-core/src/main/java/jpl/cws/core/web/CwsLdapSecurityFilter.java @@ -4,14 +4,14 @@ import java.net.HttpCookie; import java.util.Enumeration; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +31,7 @@ public class CwsLdapSecurityFilter extends CwsSecurityFilter { * all resources protected behind filter. * * (non-Javadoc) - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + * @see jakarta.servlet.Filter#doFilter(jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain) */ public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) diff --git a/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityFilter.java b/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityFilter.java index c484f6c8..b2bc8575 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityFilter.java +++ b/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityFilter.java @@ -12,11 +12,11 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.servlet.FilterConfig; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.apache.commons.io.IOUtils; import org.camunda.bpm.engine.AuthorizationService; @@ -37,7 +37,7 @@ * @author ghollins * */ -public abstract class CwsSecurityFilter implements javax.servlet.Filter { +public abstract class CwsSecurityFilter implements jakarta.servlet.Filter { private static final Logger log = LoggerFactory.getLogger(CwsSecurityFilter.class); public static final String CWS_TOKEN_COOKIE_NAME = "cwsToken"; @@ -74,9 +74,13 @@ public void init(FilterConfig filterConfig) { // so only get service here. // if (contextPath.equals("/cws-ui") || contextPath.equals("/cws-engine")) { - cwsSecurityService = (SecurityService) SpringApplicationContext.getBean("cwsSecurityService"); - authorizationService = (AuthorizationService) SpringApplicationContext.getBean("authorizationService"); - log.debug("got sec service: " + cwsSecurityService); + if (SpringApplicationContext.isContextAvailable()) { + cwsSecurityService = (SecurityService)SpringApplicationContext.getBean("cwsSecurityService"); + authorizationService = (AuthorizationService)SpringApplicationContext.getBean("authorizationService"); + log.debug("got sec service: " + cwsSecurityService); + } else { + log.error("Spring ApplicationContext not available during CwsSecurityFilter initialization."); + } } } catch (Exception e) { @@ -611,7 +615,7 @@ protected static Cookie getCwsCookieFromRequest(HttpServletRequest req) { @Override public void destroy() { // TODO Auto-generated method stub - System.out.println("CwsSecurityFilter.destroy()..."); + log.info("CwsSecurityFilter.destroy()..."); } } \ No newline at end of file diff --git a/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityValve.java b/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityValve.java index 42f6ead7..4bfe6b43 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityValve.java +++ b/cws-core/src/main/java/jpl/cws/core/web/CwsSecurityValve.java @@ -5,8 +5,8 @@ import java.io.IOException; import java.util.Enumeration; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; diff --git a/cws-core/src/main/java/jpl/cws/core/web/JsonResponse.java b/cws-core/src/main/java/jpl/cws/core/web/JsonResponse.java index ccd8e83e..ba1e26c2 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/JsonResponse.java +++ b/cws-core/src/main/java/jpl/cws/core/web/JsonResponse.java @@ -1,7 +1,7 @@ package jpl.cws.core.web; import com.google.gson.GsonBuilder; -import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.text.StringEscapeUtils; public class JsonResponse { public enum Status { diff --git a/cws-core/src/main/java/jpl/cws/core/web/WebUtils.java b/cws-core/src/main/java/jpl/cws/core/web/WebUtils.java index 89f94b61..21729e6f 100644 --- a/cws-core/src/main/java/jpl/cws/core/web/WebUtils.java +++ b/cws-core/src/main/java/jpl/cws/core/web/WebUtils.java @@ -10,9 +10,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLProtocolException; import javax.net.ssl.TrustManager; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; diff --git a/cws-engine-service/pom.xml b/cws-engine-service/pom.xml index 4bc17209..8cff384e 100644 --- a/cws-engine-service/pom.xml +++ b/cws-engine-service/pom.xml @@ -22,6 +22,10 @@ + + gov.nasa.jpl.ammos.ids.cws + cws-core + org.apache.logging.log4j @@ -30,8 +34,13 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api + + + org.apache.tomcat + tomcat-annotations-api + provided @@ -49,11 +58,29 @@ org.camunda.bpm camunda-engine + + org.camunda.bpm.model + camunda-bpmn-model + provided + + + org.camunda.commons + camunda-commons-typed-values + provided + org.camunda.bpm camunda-engine-spring + + org.camunda.bpm + camunda-engine-plugin-spin + + + org.camunda.spin + camunda-spin-core + org.camunda.spin camunda-spin-dataformat-all @@ -63,6 +90,10 @@ org.springframework spring-webmvc + + org.springframework + spring-web + org.springframework spring-test @@ -75,6 +106,15 @@ org.springframework spring-jdbc + + org.mybatis + mybatis + provided + + + org.springframework + spring-beans + org.camunda.bpm @@ -90,32 +130,39 @@ org.assertj assertj-core + + org.slf4j + slf4j-api + commons-io commons-io - org.python - jython-standalone + org.apache.commons + commons-lang3 - - de.ruedigermoeller - fst + org.apache.commons + commons-exec - - com.google.code.gson - gson + com.fasterxml.jackson.core + jackson-databind - - javax.jms - jms + org.python + jython-standalone + - javax.jms - javax.jms-api + com.google.code.gson + gson + + + + jakarta.jms + jakarta.jms-api @@ -126,13 +173,11 @@ org.graalvm.js js - 23.0.2 runtime org.graalvm.js js-scriptengine - 23.0.2 runtime @@ -145,20 +190,12 @@ org.apache.commons - commons-email + commons-email2-jakarta - - - - org.apache.activemq - activemq-client + artemis-jakarta-client org.slf4j @@ -173,22 +210,25 @@ - javax.mail - mail + jakarta.mail + jakarta.mail-api - - - org.mockito - mockito-core - + + + com.icegreen + greenmail + test + - dumbster - dumbster + org.eclipse.jetty + jetty-server + test - org.glassfish.grizzly - grizzly-http-server + org.eclipse.jetty.ee10 + jetty-ee10-servlet + test @@ -202,8 +242,8 @@ mariadb-java-client - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -226,7 +266,6 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 @@ -238,15 +277,14 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy-dependencies @@ -265,7 +303,5 @@ - - diff --git a/cws-engine-service/src/main/java/jpl/cws/controller/EngineRestService.java b/cws-engine-service/src/main/java/jpl/cws/controller/EngineRestService.java index 868ca1ed..372506e6 100644 --- a/cws-engine-service/src/main/java/jpl/cws/controller/EngineRestService.java +++ b/cws-engine-service/src/main/java/jpl/cws/controller/EngineRestService.java @@ -8,8 +8,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/CwsEngineProcessApplication.java b/cws-engine-service/src/main/java/jpl/cws/engine/CwsEngineProcessApplication.java index 379ce640..a93e8d62 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/CwsEngineProcessApplication.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/CwsEngineProcessApplication.java @@ -4,15 +4,21 @@ import java.util.Collection; import java.util.Map; -import javax.jms.Session; +import jakarta.jms.Session; import com.google.gson.Gson; import jpl.cws.core.CmdLineInputFields; -import org.apache.commons.mail.Email; -import org.apache.commons.mail.HtmlEmail; +import org.apache.commons.mail2.jakarta.Email; +import org.apache.commons.mail2.jakarta.HtmlEmail; import org.camunda.bpm.application.PostDeploy; import org.camunda.bpm.application.PreUndeploy; import org.camunda.bpm.application.ProcessApplication; +import org.camunda.bpm.application.AbstractProcessApplication; +import org.camunda.bpm.application.ProcessApplicationReference; +import org.camunda.bpm.application.ProcessApplicationUnavailableException; +import org.camunda.bpm.application.impl.ProcessApplicationReferenceImpl; +import org.camunda.bpm.engine.repository.DeploymentBuilder; + import org.camunda.bpm.engine.IdentityService; import org.camunda.bpm.engine.ProcessEngine; import org.camunda.bpm.engine.RepositoryService; @@ -27,7 +33,6 @@ import org.camunda.bpm.engine.impl.context.CoreExecutionContext; import org.camunda.bpm.engine.impl.core.instance.CoreExecution; import org.camunda.bpm.engine.impl.el.ExpressionManager; -import org.camunda.bpm.engine.spring.application.SpringServletProcessApplication; import org.camunda.bpm.model.bpmn.BpmnModelInstance; import org.camunda.bpm.model.bpmn.instance.camunda.CamundaField; import org.camunda.spin.plugin.variable.SpinValues; @@ -48,7 +53,7 @@ * Process Application exposing this application's resources the process engine. */ @ProcessApplication -public class CwsEngineProcessApplication extends SpringServletProcessApplication implements InitializingBean { +public class CwsEngineProcessApplication extends AbstractProcessApplication implements InitializingBean { @Autowired private CwsWorkerLoggerFactory cwsWorkerLoggerFactory; @Autowired private CodeService cwsCodeService; @@ -80,6 +85,16 @@ public class CwsEngineProcessApplication extends SpringServletProcessApplication private Logger log; + @Override + protected String autodetectProcessApplicationName() { + return "cws-engine-process-application"; + } + + @Override + public ProcessApplicationReference getReference() { + return new ProcessApplicationReferenceImpl(this); + } + /** * In a @PostDeploy Hook you can interact with the process engine and access * the processes the application has deployed. @@ -142,7 +157,6 @@ public void onDeploymentFinished(ProcessEngine processEngine) { // workerDaemon.setProcessApplication(getReference()); workerDaemon.start(); - workerHeartbeatDaemon.start(); workerExternalTaskLockDaemon.start(); // Update database with initial heart beat, so others will know we are alive. @@ -551,10 +565,19 @@ public void unregisterProcessApplication(ProcessEngine processEngine) { System.out.println("**************************************************"); System.out.println("****** CWS ENGINE PROCESS APP STOPPING... ******"); System.out.println("**************************************************"); - - log.warn(" Interrupting workerDaemon bean..."); + + log.warn("Stopping daemons and bringing app down..."); + + // Stop Spring-managed WorkerHeartbeatDaemon safely + workerHeartbeatDaemon.stopDaemon(); + try { + workerHeartbeatDaemon.join(5000); // wait up to 5s to terminate + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for WorkerHeartbeatDaemon to stop."); + } + workerDaemon.interrupt(); - workerHeartbeatDaemon.interrupt(); workerExternalTaskLockDaemon.interrupt(); workerService.bringWorkerDown(); diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskService.java b/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskService.java index 08122676..b5e27365 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskService.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskService.java @@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import jersey.repackaged.com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.atomic.AtomicInteger; import jpl.cws.core.log.CwsWorkerLoggerFactory; public class CwsExternalTaskService implements InitializingBean { @@ -40,10 +40,15 @@ public class CwsExternalTaskService implements InitializingBean { private static final long LOCK_DURATION = 10 * 60 * 1000L; // Initially lock external task for 10 minutes - final ThreadFactory extTaskThreadFactory = new ThreadFactoryBuilder() - .setNameFormat("extTaskThread-%d") - .setDaemon(true) - .build(); + final ThreadFactory extTaskThreadFactory = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "extTaskThread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } + }; private ExecutorService extTaskThreadPool; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskThread.java b/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskThread.java index bd6e9ee6..916a55b1 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskThread.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/CwsExternalTaskThread.java @@ -26,7 +26,7 @@ import org.apache.commons.exec.LogOutputStream; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.environment.EnvironmentUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.BatchExecutorException; import org.camunda.bpm.engine.ExternalTaskService; import org.camunda.bpm.engine.OptimisticLockingException; @@ -38,8 +38,8 @@ import com.google.gson.Gson; import edu.rice.cs.util.ArgumentTokenizer; -import jersey.repackaged.com.google.common.base.Function; -import jersey.repackaged.com.google.common.collect.Collections2; +import java.util.function.Function; +import java.util.stream.Collectors; import jpl.cws.task.CwsTaskLogger; import org.camunda.spin.json.SpinJsonNode; import org.camunda.spin.plugin.variable.SpinValues; @@ -94,7 +94,7 @@ public CwsExternalTaskThread( LockedExternalTask task, Date lockedTime) { - System.out.println("CwsExternalTaskThread constructor..."); + log.debug("CwsExternalTaskThread constructor..."); this.externalTaskService = externalTaskService; this.runtimeService = runtimeService; @@ -385,8 +385,8 @@ public List getLines() { private class StripOrderId implements Function { @SuppressWarnings("unchecked") @Override - public Object apply(Object f) { - return f.toString().replaceFirst("\\d+__", ""); + public T apply(F f) { + return (T) f.toString().replaceFirst("\\d+__", ""); } } @@ -533,7 +533,7 @@ private Boolean executeTask(CmdLineInputFields cmdInputFields, CmdLineOutputFiel // success = false; // false until proven success for (String successCode : cmdInputFields.successfulValues.split(",")) { - success = new Boolean(Integer.parseInt(successCode) == exitValue); + success = Integer.parseInt(successCode) == exitValue; if (success) { break; // found a match, so must be success } @@ -547,7 +547,7 @@ private Boolean executeTask(CmdLineInputFields cmdInputFields, CmdLineOutputFiel // Detect whether a certain event case applies (based on exit code) // for (String eventCode : exitCodeEventsMap.keySet()) { - if (new Boolean(Integer.parseInt(eventCode) == exitValue)) { + if (Integer.parseInt(eventCode) == exitValue) { cmdOutputFields.event = exitCodeEventsMap.get(eventCode); break; // can only be one event } @@ -567,12 +567,12 @@ private Boolean executeTask(CmdLineInputFields cmdInputFields, CmdLineOutputFiel // Set stdout output into variable // - String stdoutStr = StringUtils.join(Collections2.transform(stdOutLines, new StripOrderId()), '\n'); + String stdoutStr = StringUtils.join(stdOutLines.stream().map(new StripOrderId()).collect(Collectors.toList()), '\n'); cmdOutputFields.stdout = stdoutStr; // Set stderr output into variable // - String stderrStr = StringUtils.join(Collections2.transform(stdErrLines, new StripOrderId()), '\n'); + String stderrStr = StringUtils.join(stdErrLines.stream().map(new StripOrderId()).collect(Collectors.toList()), '\n'); cmdOutputFields.stderr = stderrStr; setStdOutVariables(stdOutLines); diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/EngineDbService.java b/cws-engine-service/src/main/java/jpl/cws/engine/EngineDbService.java index b8cf38bd..76970e90 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/EngineDbService.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/EngineDbService.java @@ -2,7 +2,7 @@ import jpl.cws.core.db.DbService; import jpl.cws.core.log.CwsWorkerLoggerFactory; -import org.joda.time.DateTime; +import java.time.Instant; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -163,7 +163,7 @@ public void createOrUpdateWorkerRow(String lockOwner) { int numTries = 0; String workerName = null; while (numTries++ < 10 && numUpdated != 1) { - Timestamp tsNow = new Timestamp(DateTime.now().getMillis()); + Timestamp tsNow = Timestamp.from(Instant.now()); // Determine a worker name, using current system time in milliseconds. // TODO: In the future, we might want to do a more elegant algorithm, @@ -228,7 +228,7 @@ public void createOrUpdateWorkerRow(String lockOwner) { int numUpdated = 0; int numTries = 0; while (numTries++ < 10 && numUpdated != 1) { - Timestamp tsNow = new Timestamp(DateTime.now().getMillis()); + Timestamp tsNow = Timestamp.from(Instant.now()); try { log.info("Updating row (attempt #" + numTries +", workerId='"+workerId+"') in cws_worker table..."); @@ -269,7 +269,7 @@ public void createOrUpdateWorkerRow(String lockOwner) { public void workerHeartbeat(int activeCount) { jdbcTemplate.update( "UPDATE cws_worker SET last_heartbeat_time = ?, status='up', active_count = ? WHERE id=?", - new Object[] { new Timestamp(DateTime.now().getMillis()), activeCount, workerId } + new Object[] { Timestamp.from(Instant.now()), activeCount, workerId } ); } diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerDaemon.java b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerDaemon.java index 3c5c615e..8c7680e0 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerDaemon.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerDaemon.java @@ -1,6 +1,6 @@ package jpl.cws.engine; -import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.camunda.bpm.application.ProcessApplicationReference; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerExternalTaskLockDaemon.java b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerExternalTaskLockDaemon.java index cb58a4df..0319327a 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerExternalTaskLockDaemon.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerExternalTaskLockDaemon.java @@ -69,7 +69,7 @@ public void run() { fatalErrors = 0; } catch (InterruptedException e) { - log.warn("WorkerExternalTaskLockDaemon: Error: WorkerExternalTaskLockDaemon interrupted. This is normal if the worker is being shutdown."); + log.warn("WorkerExternalTaskLockDaemon interrupted. This is normal if the worker is being shutdown."); break; } catch (Throwable t) { log.error("WorkerExternalTaskLockDaemon: Error: Error while running WorkerExternalTaskLockDaemon, " + (MAX_FAILURES - ++fatalErrors) + " retries remaining", t); @@ -82,7 +82,7 @@ public void run() { } } - log.warn("WorkerExternalTaskLockDaemon: Error: WorkerExternalTaskLockDaemon stopping..."); + log.warn("WorkerExternalTaskLockDaemon stopping..."); cwsWorkerLoggerFactory = null; externalTaskService = null; emailerService = null; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerHeartbeatDaemon.java b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerHeartbeatDaemon.java index 5ec85e47..6d183574 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerHeartbeatDaemon.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerHeartbeatDaemon.java @@ -1,19 +1,23 @@ package jpl.cws.engine; import jpl.cws.core.log.CwsEmailerService; -import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import jpl.cws.core.log.CwsWorkerLoggerFactory; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** * Heartbeat daemon for workers - * @author ztaylor */ +@Component public class WorkerHeartbeatDaemon extends Thread { @Autowired private CwsWorkerLoggerFactory cwsWorkerLoggerFactory; @Autowired private WorkerService workerService; @@ -27,60 +31,96 @@ public class WorkerHeartbeatDaemon extends Thread { private static final String LOCK_DAEMON = WorkerExternalTaskLockDaemon.class.getSimpleName(); private static final String WORKER_DAEMON = WorkerDaemon.class.getSimpleName(); - @Override - public void run() { - log = cwsWorkerLoggerFactory.getLogger(this.getClass()); - - int failures = 0; + private final AtomicBoolean running = new AtomicBoolean(false); - while (true) { - try { - - Thread.sleep(SLEEP_MS); - - // Check that daemons are still alive - Set threadSet = Thread.getAllStackTraces().keySet(); - Set threadClasses = threadSet.stream().map(thread -> thread.getClass().getSimpleName()).collect(Collectors.toSet()); - - if (threadClasses.contains(LOCK_DAEMON) && threadClasses.contains(WORKER_DAEMON)) { + public WorkerHeartbeatDaemon() { + setName("WorkerHeartbeatDaemon"); + setDaemon(true); // ensures JVM can exit even if this thread is running + } - // Send heartbeat - workerService.heartbeat(); - } - else { - // One of the daemons is down, report and break - String msg = "Detected one or more critical daemons down, exiting."; + @PostConstruct + public void startDaemon() { + log = cwsWorkerLoggerFactory.getLogger(this.getClass()); + log.info("Starting WorkerHeartbeatDaemon..."); + running.set(true); + this.start(); + } - msg += "\n\t" + WORKER_DAEMON + " is " + (threadClasses.contains(WORKER_DAEMON) ? "UP" : "DOWN"); - msg += "\n\t" + LOCK_DAEMON + " is " + (threadClasses.contains(LOCK_DAEMON) ? "UP" : "DOWN"); + @Override + public void run() { + int failures = 0; - log.error(msg); - emailerService.sendNotificationEmails("CWS Worker Error", "Severe Error!\n\nWorker heartbeat daemon encountered fatal runtime error.\n\nDetails: " + msg); + try { + while (running.get()) { - // Exit - workerService.bringWorkerDown(); + // Sleep in small chunks to respond faster to interrupts + try { + Thread.sleep(SLEEP_MS); + } catch (InterruptedException e) { + if (!running.get()) break; // shutdown requested + log.warn("WorkerHeartbeatDaemon interrupted during sleep."); + Thread.currentThread().interrupt(); break; } - failures = 0; - } catch (InterruptedException e) { - log.warn("WorkerHeartbeatDaemon interrupted. This is normal if the worker is being shutdown."); - break; - } catch (Throwable t) { - log.error("Error while running WorkerHeartbeatDaemon, " + (MAX_FAILURES - ++failures) + " retries remaining", t); - - // Stop the thread and notify admin if this daemon has failed more than 20 times - if (failures >= MAX_FAILURES) { - emailerService.sendNotificationEmails( - "CWS Worker Error", - "Severe Error!\n\nWorker heartbeat daemon encountered fatal runtime error.\n\nDetails: " + ExceptionUtils.getStackTrace(t)); - break; + if (!running.get()) break; + + try { + Set threadClasses = Thread.getAllStackTraces() + .keySet() + .stream() + .map(thread -> thread.getClass().getSimpleName()) + .collect(Collectors.toSet()); + + if (threadClasses.contains(LOCK_DAEMON) && threadClasses.contains(WORKER_DAEMON)) { + workerService.heartbeat(); + } else { + String msg = "Detected one or more critical daemons down, exiting.\n" + + "\t" + WORKER_DAEMON + " is " + (threadClasses.contains(WORKER_DAEMON) ? "UP" : "DOWN") + "\n" + + "\t" + LOCK_DAEMON + " is " + (threadClasses.contains(LOCK_DAEMON) ? "UP" : "DOWN"); + + log.error(msg); + emailerService.sendNotificationEmails( + "CWS Worker Error", + "Severe Error!\n\nWorker heartbeat daemon encountered fatal runtime error.\n\nDetails: " + msg + ); + + workerService.bringWorkerDown(); + break; + } + + } catch (Throwable t) { + log.error("Error in WorkerHeartbeatDaemon, " + (MAX_FAILURES - ++failures) + " retries remaining", t); + + if (failures >= MAX_FAILURES) { + emailerService.sendNotificationEmails( + "CWS Worker Error", + "Severe Error!\n\nWorker heartbeat daemon encountered fatal runtime error.\n\nDetails: " + + ExceptionUtils.getStackTrace(t) + ); + break; + } } } + } finally { + log.warn("WorkerHeartbeatDaemon stopping..."); + cleanupReferences(); } + } + + @PreDestroy + public void stopDaemon() { + log.info("Shutting down WorkerHeartbeatDaemon..."); + running.set(false); + this.interrupt(); + } - log.warn("WorkerHeartbeatDaemon stopping..."); + /** + * Nullify references for GC cleanup. + */ + private void cleanupReferences() { cwsWorkerLoggerFactory = null; workerService = null; + emailerService = null; } } diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerService.java b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerService.java index cb271991..1d0c7a57 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerService.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerService.java @@ -44,7 +44,7 @@ import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.listener.DefaultMessageListenerContainer; -import jersey.repackaged.com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.atomic.AtomicInteger; import jpl.cws.core.db.SchedulerDbService; import jpl.cws.core.log.CwsWorkerLoggerFactory; import jpl.cws.core.service.SpringApplicationContext; @@ -101,16 +101,26 @@ public class WorkerService implements InitializingBean { public static String lastProcCounterStatusMsg; - final ThreadFactory processorThreadFactory = new ThreadFactoryBuilder() - .setNameFormat("procReq-Processor-%d") - .setDaemon(true) - .build(); + final ThreadFactory processorThreadFactory = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "procReq-Processor-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } + }; private ExecutorService processorPool; - final ThreadFactory starterThreadFactory = new ThreadFactoryBuilder() - .setNameFormat("procReq-Starter-%d") - .setDaemon(true) - .build(); + final ThreadFactory starterThreadFactory = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "procReq-Starter-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } + }; private ExecutorService starterPool; private static long skippedMessages = 0; @@ -121,7 +131,6 @@ public class WorkerService implements InitializingBean { ObjectName serviceName; public WorkerService() { - System.out.println("WorkerService constructor..."); } public void setProcAppRef(ProcessApplicationReference procAppRef) { @@ -504,6 +513,11 @@ public void heartbeat() { } activeCount = null; } + catch (javax.management.InstanceNotFoundException e) { + // This is expected during shutdown when Camunda MBeans are being cleaned up + log.debug("JMX MBean not found during heartbeat (likely during shutdown): " + e.getMessage()); + // Don't log as error during shutdown to avoid noise + } catch (Exception e) { log.error("problem encountered during heartbeat()", e); } @@ -911,8 +925,16 @@ public boolean syncCounters(String reason) { public void setJobExecutorMaxPoolSize(Integer executorServiceMaxPoolSize, boolean doDbUpdate) { if (executorServiceMaxPoolSize != null) { try { - // we are getting errors if we go beyond 10? - executorServiceMaxPoolSize = Math.min(10, executorServiceMaxPoolSize); + // Apply safety limit based on historical issues with larger thread pools + // This was at 10, but now trying 16 + // TODO: Make this configurable and investigate if larger pools are safe + int maxAllowedPoolSize = 16; + if (executorServiceMaxPoolSize > maxAllowedPoolSize) { + log.warn("Requested pool size " + executorServiceMaxPoolSize + + " exceeds safety limit of " + maxAllowedPoolSize + + ". Limiting to " + maxAllowedPoolSize + " to prevent known issues."); + executorServiceMaxPoolSize = maxAllowedPoolSize; + } // Log information about JMX remote interface if (System.getProperty("com.sun.management.jmxremote") == null) { log.warn("JMX remote appears to be disabled"); @@ -935,18 +957,40 @@ public void setJobExecutorMaxPoolSize(Integer executorServiceMaxPoolSize, boolea // Set the "MaximumPoolSize" attribute ObjectName serviceName = new ObjectName("org.camunda.bpm.platform:type=executor-service"); - // Set the CorePoolSize. - // Through experimentation, we have seen that the max doesn't get reached, but - // its the core size that counts. - // - Attribute attr2 = new Attribute("CorePoolSize", executorServiceMaxPoolSize); - mbsc.setAttribute(serviceName, attr2); - - // Also set the MaximumPoolSize + // Get current MaximumPoolSize to determine if we're increasing or decreasing + // Note: CorePoolSize is write-only, so we can't read its current value + Integer currentMaxPoolSize = (Integer)mbsc.getAttribute(serviceName, "MaximumPoolSize"); + + boolean updatedPoolSize = false; + // ThreadPoolExecutor constraint: corePoolSize cannot be greater than maximumPoolSize + // Strategy: + // - If decreasing: lower core first, then max + // - If increasing: raise max first, then core // - // FIXME: make this attribute configurable in "advanced" configuration of configure.sh - Attribute attr = new Attribute("MaximumPoolSize", executorServiceMaxPoolSize); - mbsc.setAttribute(serviceName, attr); + if (executorServiceMaxPoolSize < currentMaxPoolSize) { + // DECREASING: Lower core first, then max + log.debug("Decreasing max pool size from " + currentMaxPoolSize + " to " + executorServiceMaxPoolSize); + + Attribute coreAttr = new Attribute("CorePoolSize", executorServiceMaxPoolSize); + mbsc.setAttribute(serviceName, coreAttr); + + Attribute maxAttr = new Attribute("MaximumPoolSize", executorServiceMaxPoolSize); + mbsc.setAttribute(serviceName, maxAttr); + + updatedPoolSize = true; + } + else if (executorServiceMaxPoolSize > currentMaxPoolSize) { + // INCREASING: Raise max first, then core + log.debug("Increasing max pool size from " + currentMaxPoolSize + " to " + executorServiceMaxPoolSize); + + Attribute maxAttr = new Attribute("MaximumPoolSize", executorServiceMaxPoolSize); + mbsc.setAttribute(serviceName, maxAttr); + + Attribute coreAttr = new Attribute("CorePoolSize", executorServiceMaxPoolSize); + mbsc.setAttribute(serviceName, coreAttr); + + updatedPoolSize = true; + } // Finally set the member variable (used to determine how many to query in claim phase) // @@ -955,8 +999,13 @@ public void setJobExecutorMaxPoolSize(Integer executorServiceMaxPoolSize, boolea // Close JMX connector jmxc.close(); - log.info("Set Job Executor max pool size to " + executorServiceMaxPoolSize + " (JMX URL: " + JMX_SERVICE_URL + ")"); - + if (updatedPoolSize) { + log.info("Set Job Executor max pool size to " + executorServiceMaxPoolSize + " (JMX URL: " + JMX_SERVICE_URL + ")"); + } + else { + log.trace("Pool size already at target value: " + executorServiceMaxPoolSize + ". No change needed."); + } + if (doDbUpdate) { // Now update the DB, since setting was successful. // @@ -1002,11 +1051,11 @@ public void bringWorkerDown() { Map beans = SpringApplicationContext.getBeansOfType(DefaultMessageListenerContainer.class); for (Entry bean : beans.entrySet()) { DefaultMessageListenerContainer container = bean.getValue(); - System.out.println(" container.stop: " + container); + log.info(" container.stop: " + container); if (container.isRunning()) { container.stop(); } - System.out.println(" container.shutdown: " + container); + log.info(" container.shutdown: " + container); container.shutdown(); } diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerStartupAndShutdown.java b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerStartupAndShutdown.java index e593f9bf..50daa296 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/WorkerStartupAndShutdown.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/WorkerStartupAndShutdown.java @@ -1,10 +1,22 @@ package jpl.cws.engine; +import java.util.Map; +import java.util.Map.Entry; + import jpl.cws.core.service.SpringApplicationContext; +import jpl.cws.core.util.NettyShutdownUtil; import org.camunda.bpm.engine.ProcessEngine; +import org.springframework.jms.connection.CachingConnectionFactory; +import org.springframework.jms.listener.DefaultMessageListenerContainer; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; + +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service class for worker. @@ -14,13 +26,138 @@ */ public class WorkerStartupAndShutdown implements ServletContextListener { + private static final Logger log = LoggerFactory.getLogger(WorkerStartupAndShutdown.class); /** * This method gets called when tomcat is shutting down. */ @Override public void contextDestroyed(ServletContextEvent arg0) { - System.out.println("Worker detected that tomcat is going down."); + log.info("Worker detected that tomcat is going down."); + + // Step 1: Shutdown message listener containers first + log.info(" Shutting down DefaultMessageListenerContainers..."); + if (SpringApplicationContext.isContextAvailable()) { + try { + Map beans = SpringApplicationContext.getBeansOfType(DefaultMessageListenerContainer.class); + for (Entry bean : beans.entrySet()) { + DefaultMessageListenerContainer container = bean.getValue(); + log.debug(" container.stop: " + container); + if (container.isRunning()) { + container.stop(); + } + log.debug(" container.shutdown: " + container); + container.shutdown(); + } + } catch (Exception e) { + log.error(" Error shutting down message listener containers: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping message listener container shutdown"); + } + + // Step 2: Properly close connection factory and underlying connections + if (SpringApplicationContext.isContextAvailable()) { + try { + log.info(" Shutting down connection factory..."); + CachingConnectionFactory cc = (CachingConnectionFactory)SpringApplicationContext.getBean("cachingConnectionFactory"); + if (cc != null) { + // Close all cached connections + cc.resetConnection(); + cc.destroy(); + log.info(" CachingConnectionFactory destroyed successfully."); + } + + // Also close the underlying ActiveMQ connection factory + ActiveMQConnectionFactory acf = (ActiveMQConnectionFactory)SpringApplicationContext.getBean("connectionFactory"); + if (acf != null) { + acf.close(); + log.info(" ActiveMQConnectionFactory closed successfully."); + } + } catch (Exception e) { + log.error(" Error destroying connection factory: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping connection factory shutdown"); + } + + // Step 3: Stop worker daemons manually + if (SpringApplicationContext.isContextAvailable()) { + try { + log.info(" Stopping worker daemons..."); + WorkerHeartbeatDaemon workerHeartbeatDaemon = (WorkerHeartbeatDaemon) + SpringApplicationContext.getBean("workerHeartbeatDaemon"); + WorkerDaemon workerDaemon = (WorkerDaemon) + SpringApplicationContext.getBean("workerDaemon"); + WorkerExternalTaskLockDaemon workerExternalTaskLockDaemon = (WorkerExternalTaskLockDaemon) + SpringApplicationContext.getBean("workerExternalTaskLockDaemon"); + WorkerService workerService = (WorkerService) + SpringApplicationContext.getBean("workerService"); + + if (workerHeartbeatDaemon != null) { + log.info(" Stopping WorkerHeartbeatDaemon..."); + workerHeartbeatDaemon.stopDaemon(); + try { + workerHeartbeatDaemon.join(5000); // wait up to 5s to terminate + if (workerHeartbeatDaemon.isAlive()) { + log.warn(" WorkerHeartbeatDaemon did not stop gracefully within 5 seconds"); + } else { + log.info(" WorkerHeartbeatDaemon stopped successfully"); + } + } catch (InterruptedException e) { + log.warn(" Interrupted while waiting for WorkerHeartbeatDaemon to stop"); + Thread.currentThread().interrupt(); + } + } + + if (workerDaemon != null) { + log.info(" Interrupting WorkerDaemon..."); + workerDaemon.interrupt(); + } + + if (workerExternalTaskLockDaemon != null) { + log.info(" Interrupting WorkerExternalTaskLockDaemon..."); + workerExternalTaskLockDaemon.interrupt(); + } + + if (workerService != null) { + log.info(" Bringing worker down..."); + workerService.bringWorkerDown(); + } + + log.info(" Worker daemons stopped successfully."); + } catch (Exception e) { + log.error(" Error stopping worker daemons: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping worker daemon shutdown"); + } + + // Step 4: Force shutdown of any remaining Netty threads and MySQL cleanup + try { + log.info(" Forcing shutdown of Netty threads and MySQL cleanup..."); + + // First, try to gracefully shutdown client-side Netty components + if (SpringApplicationContext.isContextAvailable()) { + try { + ActiveMQConnectionFactory acf = (ActiveMQConnectionFactory)SpringApplicationContext.getBean("connectionFactory"); + if (acf != null) { + NettyShutdownUtil.shutdownClientNettyComponents(acf); + } + } catch (Exception e) { + log.error(" Error accessing connection factory bean during Netty cleanup: " + e.getMessage(), e); + } + } + + // Shutdown MySQL connection cleanup threads + NettyShutdownUtil.shutdownMysqlConnectionCleanup(); + + // Use the utility class for comprehensive Netty cleanup + NettyShutdownUtil.forceShutdownNettyThreads(); + + } catch (Exception e) { + log.error(" Error during Netty thread cleanup: " + e.getMessage(), e); + } } @@ -29,14 +166,17 @@ public void contextDestroyed(ServletContextEvent arg0) { */ @Override public void contextInitialized(ServletContextEvent arg0) { - System.out.println("Worker detected that tomcat is coming up."); + log.info("Worker detected that tomcat is coming up."); + + // Register Netty shutdown hook as a safety net + NettyShutdownUtil.registerShutdownHook(); CwsEngineProcessApplication app = (CwsEngineProcessApplication) SpringApplicationContext.getBean("cwsEngineProcessApplication"); - System.out.println("CwsEngineProcessApplication = " + app); + log.info("CwsEngineProcessApplication = " + app); ProcessEngine pe = (ProcessEngine) SpringApplicationContext.getBean("processEngine2"); - System.out.println("ProcessEngine = " + pe); + log.info("ProcessEngine = " + pe); // // Startup CwsEngineProcessApplication, now that tomcat is up and running diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/CodeUpdateListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/CodeUpdateListener.java index a9042dac..af255d8e 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/CodeUpdateListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/CodeUpdateListener.java @@ -1,8 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessEventListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessEventListener.java index b439fa6e..75b3b831 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessEventListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessEventListener.java @@ -1,8 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessExternalTasksListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessExternalTasksListener.java index 741cc830..ce503d33 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessExternalTasksListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessExternalTasksListener.java @@ -1,9 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; -import javax.jms.Session; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStartRequestListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStartRequestListener.java index afe517a4..a1f605ae 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStartRequestListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStartRequestListener.java @@ -1,8 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStarterThread.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStarterThread.java index 571103e5..fe1318b7 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStarterThread.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/ProcessStarterThread.java @@ -1,22 +1,19 @@ package jpl.cws.engine.listener; -import static java.lang.Thread.sleep; -import static jpl.cws.core.db.SchedulerDbService.CLAIMED_BY_WORKER; -import static jpl.cws.core.db.SchedulerDbService.FAILED_TO_START; -import static jpl.cws.core.db.SchedulerDbService.PENDING; - -import java.io.ByteArrayInputStream; -import java.util.HashMap; -import java.util.Map; - +import com.fasterxml.jackson.databind.ObjectMapper; +import jpl.cws.core.db.SchedulerDbService; +import jpl.cws.engine.WorkerService; import org.camunda.bpm.engine.RepositoryService; import org.camunda.bpm.engine.RuntimeService; import org.camunda.bpm.engine.repository.ProcessDefinition; import org.slf4j.Logger; -import de.ruedigermoeller.serialization.FSTObjectInput; -import jpl.cws.core.db.SchedulerDbService; -import jpl.cws.engine.WorkerService; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static java.lang.Thread.sleep; +import static jpl.cws.core.db.SchedulerDbService.*; /** * Thread responsible for starting a new process instance in the Camunda engine. @@ -100,13 +97,13 @@ public void run() { "Process definition with proc def key of: '" + procDefKey + "' is suspended, so not starting!"); } - - // Get process variables as a map - // - byte[] procVarsAsBytes = (byte[])procReq.get("proc_variables"); - FSTObjectInput in = new FSTObjectInput(new ByteArrayInputStream(procVarsAsBytes)); - Map procVars = (Map)in.readObject(); - in.close(); + + // Get process variables as a map + // + byte[] procVarsAsBytes = (byte[])procReq.get("proc_variables"); + String json = new String(procVarsAsBytes, StandardCharsets.UTF_8).trim(); + ObjectMapper objectMapper = new ObjectMapper(); + Map procVars = objectMapper.readValue(json, Map.class); if (procVars == null) { procVars = new HashMap(); } diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerConfigChangeListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerConfigChangeListener.java index d0a38126..e76e404c 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerConfigChangeListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerConfigChangeListener.java @@ -1,8 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerLogCleanupListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerLogCleanupListener.java index 1fc6c3f0..1ef62530 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerLogCleanupListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerLogCleanupListener.java @@ -2,11 +2,12 @@ import java.util.Date; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; -import org.joda.time.LocalDate; +import java.time.LocalDate; +import java.time.ZoneId; import org.slf4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +47,7 @@ public void onMessage(final Message message) { log.debug("GOT historyDaysToLive: " + historyDaysToLive); - Date keepDate = LocalDate.now().plusDays(-historyDaysToLive).toDate(); + Date keepDate = Date.from(LocalDate.now().plusDays(-historyDaysToLive).atStartOfDay(ZoneId.systemDefault()).toInstant()); workerService.cleanupLogs(keepDate); } diff --git a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerShutdownListener.java b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerShutdownListener.java index 6536b947..8d10e4dd 100644 --- a/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerShutdownListener.java +++ b/cws-engine-service/src/main/java/jpl/cws/engine/listener/WorkerShutdownListener.java @@ -1,8 +1,8 @@ package jpl.cws.engine.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecuteResultHandler; diff --git a/cws-engine/pom.xml b/cws-engine/pom.xml index d1c448cc..a0a2fdbf 100644 --- a/cws-engine/pom.xml +++ b/cws-engine/pom.xml @@ -27,28 +27,25 @@ org.graalvm.js js - 23.0.2 runtime org.graalvm.js js-scriptengine - 23.0.2 runtime - ${project.artifactId} maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true diff --git a/cws-engine/src/main/webapp/WEB-INF/web.xml b/cws-engine/src/main/webapp/WEB-INF/web.xml index 11a83f75..469a20a9 100644 --- a/cws-engine/src/main/webapp/WEB-INF/web.xml +++ b/cws-engine/src/main/webapp/WEB-INF/web.xml @@ -1,9 +1,9 @@ - + xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee + https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" + version="6.0"> cws-engine-web diff --git a/cws-installer/pom.xml b/cws-installer/pom.xml index 67598c8d..459181c7 100644 --- a/cws-installer/pom.xml +++ b/cws-installer/pom.xml @@ -18,11 +18,16 @@ log4j-slf4j-impl provided + + org.slf4j + slf4j-api + provided + - commons-lang - commons-lang + org.apache.commons + commons-lang3 commons-io @@ -34,22 +39,17 @@ org.apache.commons - commons-email + commons-email2-jakarta - commons-configuration - commons-configuration + org.apache.commons + commons-configuration2 org.apache.commons commons-compress - - joda-time - joda-time - - @@ -57,8 +57,8 @@ mariadb-java-client - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -80,15 +80,14 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy-dependencies @@ -120,7 +119,5 @@ - - diff --git a/cws-installer/src/main/java/jpl/cws/task/CwsInstaller.java b/cws-installer/src/main/java/jpl/cws/task/CwsInstaller.java index 0a5bf944..02d69e40 100755 --- a/cws-installer/src/main/java/jpl/cws/task/CwsInstaller.java +++ b/cws-installer/src/main/java/jpl/cws/task/CwsInstaller.java @@ -82,7 +82,7 @@ import javax.tools.ToolProvider; -import org.joda.time.DateTime; +import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1816,7 +1816,7 @@ private static void showInstallationInfo() { print("CWS Notification Emails = " + cws_notification_emails); print("CWS Token Expiration In Hours = " + cws_token_expiration_hours); print("History Level = " + history_level); - print("Processes per Worker = " + worker_max_num_running_procs); + print("Max Num Processes per Worker = " + worker_max_num_running_procs); print("Days Remove Abandoned Workers = " + worker_abandoned_days); if (installConsole) { print("History Days to Live = " + history_days_to_live); @@ -1986,7 +1986,7 @@ private static int validateDbConfig() { print(""); print("checking that database timestamp is consistent with this installation timestamp..."); long q0 = System.currentTimeMillis(); - Timestamp thisMachineTime = new Timestamp(DateTime.now().getMillis()); + Timestamp thisMachineTime = Timestamp.from(Instant.now()); Timestamp databaseTime; try (ResultSet rs = statm.executeQuery("SELECT CURRENT_TIMESTAMP()")) { @@ -2625,6 +2625,8 @@ private static void createFreshWorkDir() { Paths.get(config_work_dir + SEP + "cws-ui" + SEP + "cws-ui.properties")); copy(Paths.get( config_templates_dir + SEP + "cws-ui" + SEP + "applicationContext.xml"), Paths.get(config_work_dir + SEP + "cws-ui" + SEP + "applicationContext.xml")); + copy(Paths.get( config_templates_dir + SEP + "cws-ui" + SEP + "broker.xml"), + Paths.get(config_work_dir + SEP + "cws-ui" + SEP + "broker.xml")); copyAllType( config_templates_dir + SEP + "cws-ui", config_work_dir + SEP + "cws-ui", "ftl"); @@ -2814,6 +2816,25 @@ private static void updateFiles() throws IOException { Paths.get(config_work_dir + SEP + "engine-rest_mods" + SEP + "web.xml"), Paths.get(cws_tomcat_webapps + SEP + "engine-rest" + SEP + "WEB-INF" + SEP + "web.xml")); + // CREATE ARTEMIS DIRECTORY STRUCTURE AND COPY BROKER.XML + print(" Creating Artemis directory structure and copying broker.xml..."); + mkDir(cws_root + SEP + "server" + SEP + "artemis"); + mkDir(cws_root + SEP + "server" + SEP + "artemis" + SEP + "etc"); + mkDir(cws_root + SEP + "server" + SEP + "artemis" + SEP + "data"); + mkDir(cws_root + SEP + "server" + SEP + "artemis" + SEP + "log"); + mkDir(cws_root + SEP + "server" + SEP + "artemis" + SEP + "tmp"); + + // Process broker.xml and replace placeholders + print(" Processing broker.xml and replacing placeholders..."); + Path brokerXmlPath = Paths.get(config_work_dir + SEP + "cws-ui" + SEP + "broker.xml"); + String brokerContent = getFileContents(brokerXmlPath); + brokerContent = brokerContent.replace("__CWS_AMQ_HOST__", cws_amq_host); + brokerContent = brokerContent.replace("__CWS_AMQ_PORT__", cws_amq_port); + brokerContent = brokerContent.replace("__CWS_ROOT_DIR__", cws_root); + + Path targetBrokerPath = Paths.get(cws_root + SEP + "server" + SEP + "artemis" + SEP + "etc" + SEP + "broker.xml"); + writeToFile(targetBrokerPath, brokerContent); + deleteDirectory(new File(cws_tomcat_webapps + SEP + "h2")); deleteDirectory(new File(cws_tomcat_webapps + SEP + "manager")); deleteDirectory(new File(cws_tomcat_webapps + SEP + "host-manager")); diff --git a/cws-installer/src/main/resources/log4j2.properties b/cws-installer/src/main/resources/log4j2.properties index 8eff02ea..d4d1a5c6 100644 --- a/cws-installer/src/main/resources/log4j2.properties +++ b/cws-installer/src/main/resources/log4j2.properties @@ -6,7 +6,7 @@ #log4j.appender.CA.layout.ConversionPattern=INSTALLER: %d{ISO8601}{UTC} %-5p [%20.20t] %30.30c(%4.4L) - %m%n #log4j.logger.org.springframework=INFO #log4j.logger.jpl.cws=INFO -#log4j.logger.javax.activation.level=INFO +#log4j.logger.jakarta.activation.level=INFO # #log4j.logger.org.apache.activemq.transport=INFO # @@ -34,11 +34,11 @@ logger.cws.level = info logger.cws.additivity = false logger.cws.appenderRef.stdout.ref = STDOUT -# Javax -logger.javax.name = javax.activation.level -logger.javax.level = info -logger.javax.additivity = false -logger.javax.appenderRef.stdout.ref = STDOUT +# Jakarta +logger.jakarta.name = jakarta.activation.level +logger.jakarta.level = info +logger.jakarta.additivity = false +logger.jakarta.appenderRef.stdout.ref = STDOUT # ActiveMQ logger.activemq.name = org.apache.activemq.transport diff --git a/cws-service/pom.xml b/cws-service/pom.xml index f0bd2f5e..9f9285d3 100644 --- a/cws-service/pom.xml +++ b/cws-service/pom.xml @@ -25,8 +25,98 @@ - org.springframework - spring-test + org.springframework + spring-test + + + + org.springframework + spring-jdbc + + + org.springframework + spring-jms + + + org.springframework + spring-web + + + org.springframework + spring-webmvc + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + + org.apache.activemq + artemis-core-client + + + org.apache.activemq + artemis-server + + + + software.amazon.awssdk + regions + + + software.amazon.awssdk + aws-core + + + software.amazon.awssdk + sdk-core + + + + org.camunda.bpm + camunda-engine + + + org.camunda.bpm.model + camunda-bpmn-model + provided + + + org.camunda.bpm.model + camunda-xml-model + provided + + + org.camunda.commons + camunda-commons-typed-values + provided + + + + org.slf4j + slf4j-api + + + + io.swagger.core.v3 + swagger-models-jakarta + + + io.swagger.core.v3 + swagger-annotations-jakarta + + + + commons-io + commons-io @@ -37,6 +127,12 @@ software.amazon.awssdk apache-client + + + commons-logging + commons-logging + + software.amazon.awssdk @@ -48,14 +144,17 @@ - javax.servlet - javax.servlet-api - - - - javax.annotation - javax.annotation-api + jakarta.servlet + jakarta.servlet-api + + jakarta.annotation + jakarta.annotation-api + + + jakarta.jms + jakarta.jms-api + org.tuckey @@ -93,11 +192,6 @@ mybatis - - joda-time - joda-time - - org.apache.logging.log4j log4j-slf4j-impl @@ -122,8 +216,8 @@ - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -137,15 +231,9 @@ commons-fileupload - - - org.apache.activemq - activemq-broker - - org.apache.activemq - activemq-client + artemis-jakarta-client org.slf4j @@ -153,26 +241,9 @@ - org.apache.activemq - activeio-core - - - log4j - log4j - - - - - - org.apache.activemq - activemq-kahadb-store - - - - org.apache.activemq - activemq-spring + artemis-jakarta-server org.slf4j @@ -181,31 +252,6 @@ - - org.apache.geronimo.specs - geronimo-jms_1.1_spec - - - - org.apache.geronimo.specs - geronimo-jta_1.0.1B_spec - - - - org.apache.geronimo.specs - geronimo-j2ee-management_1.1_spec - - - - org.apache.geronimo.specs - geronimo-jacc_1.1_spec - - - - org.apache.geronimo.specs - geronimo-j2ee-connector_1.5_spec - - com.google.code.gson gson @@ -216,11 +262,6 @@ commons-exec - - org.apache.xbean - xbean-spring - - org.springframework spring-context-support @@ -259,22 +300,47 @@ bootstrap - io.springfox - springfox-swagger2 + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 - ${project.artifactId} maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + + -parameters + diff --git a/cws-service/src/main/java/jpl/cws/console/AwsMetricsPublisherBackgroundThread.java b/cws-service/src/main/java/jpl/cws/console/AwsMetricsPublisherBackgroundThread.java index 95f09369..16d11d56 100644 --- a/cws-service/src/main/java/jpl/cws/console/AwsMetricsPublisherBackgroundThread.java +++ b/cws-service/src/main/java/jpl/cws/console/AwsMetricsPublisherBackgroundThread.java @@ -1,7 +1,7 @@ package jpl.cws.console; import jpl.cws.core.db.SchedulerDbService; -import org.joda.time.DateTime; +import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -74,7 +74,7 @@ public void run() { // Publish the number of workers metric to AWS int numWorkers = getNumActiveWorkers(); - publishMetric(NUM_ACTIVE_WORKERS_METRIC_NAME, new Double(numWorkers)); + publishMetric(NUM_ACTIVE_WORKERS_METRIC_NAME, (double)numWorkers); } catch (InterruptedException e) { log.warn("AwsMetricsPublisherBackgroundThread interrupted. Must be shutting down.."); @@ -126,7 +126,7 @@ private void initCloudWatch() throws Exception { * */ private Long getMaxPendingQueueTime() { - Timestamp now = new Timestamp(DateTime.now().getMillis()); + Timestamp now = Timestamp.from(Instant.now()); // Get list of scheduled rows in the 'pending' state List> pendingRows = schedulerDbService.getPendingProcessInstances(); @@ -195,7 +195,7 @@ private int getNumActiveWorkers() { */ private void publishMetric(String metricName, Double metricValue) { if (metricValue == null) { - metricValue = new Double(0); // still publish a data point.. + metricValue = 0.0; // still publish a data point.. } try { diff --git a/cws-service/src/main/java/jpl/cws/console/CwsStartupAndShutdown.java b/cws-service/src/main/java/jpl/cws/console/CwsStartupAndShutdown.java index 3c1658a7..96b100c9 100644 --- a/cws-service/src/main/java/jpl/cws/console/CwsStartupAndShutdown.java +++ b/cws-service/src/main/java/jpl/cws/console/CwsStartupAndShutdown.java @@ -3,18 +3,28 @@ import java.util.Map; import java.util.Map.Entry; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; import jpl.cws.core.log.CwsEmailerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.listener.DefaultMessageListenerContainer; +import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import jpl.cws.core.service.SpringApplicationContext; +import jpl.cws.core.util.NettyShutdownUtil; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CwsStartupAndShutdown implements ServletContextListener { + private static final Logger log = LoggerFactory.getLogger(CwsStartupAndShutdown.class); + public static boolean isShuttingDown = false; public CwsStartupAndShutdown() { @@ -24,33 +34,131 @@ public CwsStartupAndShutdown() { @Override public void contextDestroyed(ServletContextEvent arg0) { isShuttingDown = true; - System.out.println("cws-ui web app has detected tomcat shutdown."); - System.out.println(" Shutting down DefaultMessageListenerContainers..."); - Map beans = SpringApplicationContext.getBeansOfType(DefaultMessageListenerContainer.class); - for (Entry bean : beans.entrySet()) { - DefaultMessageListenerContainer container = bean.getValue(); - System.out.println(" A container.stop: " + container); - if (container.isRunning()) { - container.stop(); + log.info("cws-ui web app has detected tomcat shutdown."); + + // Step 1: Shutdown message listener containers first + log.info(" Shutting down DefaultMessageListenerContainers..."); + if (SpringApplicationContext.isContextAvailable()) { + try { + Map beans = SpringApplicationContext.getBeansOfType(DefaultMessageListenerContainer.class); + for (Entry bean : beans.entrySet()) { + DefaultMessageListenerContainer container = bean.getValue(); + log.debug(" container.stop: " + container); + if (container.isRunning()) { + container.stop(); + } + log.debug(" container.shutdown: " + container); + container.shutdown(); + } + } catch (Exception e) { + log.error(" Error shutting down message listener containers: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping message listener container shutdown"); + } + + // Step 2: Properly close connection factory and underlying connections + if (SpringApplicationContext.isContextAvailable()) { + try { + log.info(" Shutting down connection factory..."); + CachingConnectionFactory cc = (CachingConnectionFactory)SpringApplicationContext.getBean("cachingConnectionFactory"); + if (cc != null) { + // Close all cached connections + cc.resetConnection(); + cc.destroy(); + log.info(" CachingConnectionFactory destroyed successfully."); + } + + // Also close the underlying ActiveMQ connection factory + ActiveMQConnectionFactory acf = (ActiveMQConnectionFactory)SpringApplicationContext.getBean("connectionFactory"); + if (acf != null) { + acf.close(); + log.info(" ActiveMQConnectionFactory closed successfully."); + } + } catch (Exception e) { + log.error(" Error destroying connection factory: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping connection factory shutdown"); + } + + // Step 3: Force shutdown of any remaining Netty threads and MySQL cleanup + try { + log.info(" Forcing shutdown of Netty threads and MySQL cleanup..."); + + // First, try to gracefully shutdown Netty event loop groups and thread pools + if (SpringApplicationContext.isContextAvailable()) { + try { + EmbeddedActiveMQ activeMQServer = (EmbeddedActiveMQ)SpringApplicationContext.getBean("activeMQServer"); + if (activeMQServer != null && activeMQServer.getActiveMQServer() != null) { + // Shutdown thread pools first to prevent IllegalMonitorStateException + NettyShutdownUtil.shutdownActiveMQThreadPools(activeMQServer.getActiveMQServer()); + // Then shutdown Netty event loop groups + NettyShutdownUtil.shutdownNettyEventLoopGroups(activeMQServer.getActiveMQServer()); + } + } catch (Exception e) { + log.error(" Error accessing ActiveMQ server bean during Netty cleanup: " + e.getMessage(), e); + } } - System.out.println(" A container.shutdown: " + container); - container.shutdown(); + + // Shutdown MySQL connection cleanup threads + NettyShutdownUtil.shutdownMysqlConnectionCleanup(); + + // Use the utility class for comprehensive Netty cleanup + NettyShutdownUtil.forceShutdownNettyThreads(); + + } catch (Exception e) { + log.error(" Error during Netty thread cleanup: " + e.getMessage(), e); } - CachingConnectionFactory cc = (CachingConnectionFactory)SpringApplicationContext.getBean("cachingConnectionFactory"); - cc.destroy(); + // Step 4: Shutdown ActiveMQ server last + if (SpringApplicationContext.isContextAvailable()) { + try { + log.info(" Shutting down ActiveMQ server..."); + EmbeddedActiveMQ activeMQServer = (EmbeddedActiveMQ)SpringApplicationContext.getBean("activeMQServer"); + if (activeMQServer != null) { + log.info("INFO: ActiveMQ server found, stopping..."); + activeMQServer.stop(); + log.info(" ActiveMQ server stopped successfully."); + } else { + log.info("INFO: ActiveMQ server bean is null during shutdown"); + } + } catch (Exception e) { + log.error(" Error stopping ActiveMQ server: " + e.getMessage(), e); + } + } else { + log.warn(" Spring context not available, skipping ActiveMQ server shutdown"); + } } @Override public void contextInitialized(ServletContextEvent arg0) { - System.out.println("cws-ui web app has detected tomcat startup."); + log.info("cws-ui web app has detected tomcat startup."); + + // Register Netty shutdown hook as a safety net + NettyShutdownUtil.registerShutdownHook(); + + // Add focused logging for Artemis startup + log.info("INFO: Starting CWS with Artemis debugging enabled..."); + + try { + log.debug("DEBUG: Attempting to get ActiveMQ server bean..."); + EmbeddedActiveMQ activeMQServer = (EmbeddedActiveMQ)SpringApplicationContext.getBean("activeMQServer"); + if (activeMQServer != null) { + log.debug("DEBUG: ActiveMQ server bean found: " + activeMQServer); + } else { + log.error("ERROR: ActiveMQ server bean is null - startup may fail"); + } + } catch (Exception e) { + log.error("ERROR: Failed to get ActiveMQ server bean: " + e.getMessage(), e); + } + try { CwsEmailerService cwsEmailerService = (CwsEmailerService)SpringApplicationContext.getBean("cwsEmailerService"); cwsEmailerService.sendNotificationEmails("CWS Startup", "CWS Starting up..."); } catch (Throwable t) { - System.out.println("ERROR: sending email on startup failed."); - t.printStackTrace(); + log.error("ERROR: sending email on startup failed.", t); } } diff --git a/cws-service/src/main/java/jpl/cws/console/ExternalTaskDaemon.java b/cws-service/src/main/java/jpl/cws/console/ExternalTaskDaemon.java index 4a1ff977..14f5b7b0 100644 --- a/cws-service/src/main/java/jpl/cws/console/ExternalTaskDaemon.java +++ b/cws-service/src/main/java/jpl/cws/console/ExternalTaskDaemon.java @@ -7,7 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; -import javax.jms.Session; +import jakarta.jms.Session; public class ExternalTaskDaemon extends Thread { private static final Logger log = LoggerFactory.getLogger(ElasticAndWorkerCleanupDaemon.class); diff --git a/cws-service/src/main/java/jpl/cws/console/listener/ConsoleShutdownListener.java b/cws-service/src/main/java/jpl/cws/console/listener/ConsoleShutdownListener.java index 0cb939f4..5a70ac77 100644 --- a/cws-service/src/main/java/jpl/cws/console/listener/ConsoleShutdownListener.java +++ b/cws-service/src/main/java/jpl/cws/console/listener/ConsoleShutdownListener.java @@ -1,8 +1,8 @@ package jpl.cws.console.listener; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecuteResultHandler; diff --git a/cws-service/src/main/java/jpl/cws/console/listener/ScheduleProcessListener.java b/cws-service/src/main/java/jpl/cws/console/listener/ScheduleProcessListener.java index 6f5e6b8b..4abdc948 100644 --- a/cws-service/src/main/java/jpl/cws/console/listener/ScheduleProcessListener.java +++ b/cws-service/src/main/java/jpl/cws/console/listener/ScheduleProcessListener.java @@ -2,9 +2,9 @@ import java.util.Map; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/cws-service/src/main/java/jpl/cws/controller/MvcCore.java b/cws-service/src/main/java/jpl/cws/controller/MvcCore.java index 31ea6a81..3403872e 100755 --- a/cws-service/src/main/java/jpl/cws/controller/MvcCore.java +++ b/cws-service/src/main/java/jpl/cws/controller/MvcCore.java @@ -254,10 +254,10 @@ protected ModelAndView buildWorkersModel() { model.addObject("workersTitle", cwsConsoleService.getWorkersTitle()); try { - Set clients = cwsSchedulerUtils.getAmqClients(); - model.addObject("amqClients", clients); + Set servers = cwsSchedulerUtils.getAmqClients(); + model.addObject("amqClients", servers); } catch (Exception e) { - log.error("There was a problem getting listing of AMQ clients", e); + log.error("There was a problem getting listing of AMQ servers", e); } return model; diff --git a/cws-service/src/main/java/jpl/cws/controller/MvcService.java b/cws-service/src/main/java/jpl/cws/controller/MvcService.java index 9aadd291..4bf0f5f2 100644 --- a/cws-service/src/main/java/jpl/cws/controller/MvcService.java +++ b/cws-service/src/main/java/jpl/cws/controller/MvcService.java @@ -10,9 +10,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.camunda.bpm.engine.AuthorizationService; import org.camunda.bpm.engine.IdentityService; @@ -31,7 +31,7 @@ import jpl.cws.process.initiation.InitiatorsService; import jpl.cws.scheduler.Scheduler; import jpl.cws.service.CwsConsoleService; -import springfox.documentation.annotations.ApiIgnore; +import io.swagger.v3.oas.annotations.Hidden; @Controller public class MvcService extends MvcCore { @@ -51,17 +51,23 @@ public MvcService() {} /** * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/login", method = GET) - public ModelAndView login(final HttpSession session) { - return buildModel("login", "Please log in"); + public ModelAndView login(final HttpSession session, HttpServletRequest request) { + ModelAndView model = buildModel("login", "Please log in"); + + // Add request parameters to the model for FreeMarker template access + Map parameterMap = request.getParameterMap(); + model.addObject("RequestParameters", parameterMap); + + return model; } /** * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/not_authorized", method = GET) public ModelAndView notAuthorized(final HttpSession session) { return buildModel("not_authorized", "Not authorized. Please navigate elsewhere"); @@ -71,7 +77,7 @@ public ModelAndView notAuthorized(final HttpSession session) { /** * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/home", method = GET) public ModelAndView index(final HttpSession session) { return buildHomeModel(""); @@ -80,7 +86,7 @@ public ModelAndView index(final HttpSession session) { /** * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/summary", method = GET) public ModelAndView summary(final HttpSession session) { return buildSummaryModel(""); @@ -92,7 +98,7 @@ public ModelAndView summary(final HttpSession session) { * and results in displaying the home page with a welcome message * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/logintotarget", method = POST) public ModelAndView logintotarget( final HttpSession session, @@ -147,25 +153,25 @@ public ModelAndView logintotarget( } } - @ApiIgnore + @Hidden @RequestMapping(value = "/deployments", method = GET) public ModelAndView deployments() { return buildDeploymentsModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/logs", method = GET) public ModelAndView logs() { return buildLogsModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/history", method = GET) public ModelAndView history() { return buildHistoryModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/workers", method = GET) public ModelAndView workers() { return buildWorkersModel(); @@ -176,7 +182,7 @@ public ModelAndView workers() { * Returns Initiators page model and view * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/initiators", method = GET) public ModelAndView initiators() { ModelAndView model = new ModelAndView("initiators"); @@ -195,7 +201,7 @@ public ModelAndView initiators() { return model; } - @ApiIgnore + @Hidden @RequestMapping(value = "/snippets", method = GET) public ModelAndView snippets() { ModelAndView model = new ModelAndView("snippets"); @@ -204,31 +210,31 @@ public ModelAndView snippets() { return model; } - @ApiIgnore + @Hidden @RequestMapping(value = "/processes", method = GET) public ModelAndView processes() { return buildProcessesModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/configuration", method = GET) public ModelAndView configuration() { return buildConfigurationModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/documentation", method = GET) public ModelAndView documentation() { return buildDocumentationModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/modeler", method = GET) public ModelAndView modeler() { return buildModelerModel(""); } - @ApiIgnore + @Hidden @RequestMapping(value = "/api-docs", method = GET) public ModelAndView apidocs() { return buildApiDocsModel(""); @@ -238,7 +244,7 @@ public ModelAndView apidocs() { /** * */ - @ApiIgnore + @Hidden @RequestMapping(value = "/logout", method = GET) public ModelAndView logout( final HttpSession session, diff --git a/cws-service/src/main/java/jpl/cws/controller/OpenApiController.java b/cws-service/src/main/java/jpl/cws/controller/OpenApiController.java new file mode 100644 index 00000000..9974ec7d --- /dev/null +++ b/cws-service/src/main/java/jpl/cws/controller/OpenApiController.java @@ -0,0 +1,257 @@ +package jpl.cws.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.BooleanSchema; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Controller +@RequestMapping("/v3") +public class OpenApiController { + + @Autowired + private ApplicationContext applicationContext; + + @GetMapping(value = "/api-docs", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public String getApiDocs() { + try { + OpenAPI openAPI = createOpenApiSpec(); + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL); + String json = mapper.writeValueAsString(openAPI); + + // Temporarily disable JSON cleaning to test security schemes + // json = json.replace("\"$ref\":null,", ""); + // json = json.replace("\"$ref\":null", ""); + // json = json.replaceAll(",\\s*}", "}"); // Remove trailing commas + // json = json.replaceAll(",\\s*]", "]"); // Remove trailing commas in arrays + + return json; + } catch (Exception e) { + e.printStackTrace(); + return "{\"error\": \"Failed to generate API docs\"}"; + } + } + + private OpenAPI createOpenApiSpec() { + OpenAPI openAPI = new OpenAPI() + .info(new Info() + .title("CWS API") + .description("Documentation of the endpoints used by CWS. Once authenticated, requests can be made to these endpoints.\nTo authenticate, right click on this page --> Inspect --> Click the 'Application' tab --> Select the URL under the Cookies tab on the left --> Copy the value of the cwsToken cookie.") + .version("2.7.0") + .license(new License() + .name("Apache 2.0") + .url("https://github.com/NASA-AMMOS/common-workflow-service?tab=Apache-2.0-1-ov-file"))) + .addSecurityItem(new SecurityRequirement().addList("cwsToken")) + .components(new Components() + .addSecuritySchemes("cwsToken", new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("cwsToken") + .description("CWS authentication token"))); + + // Generate paths from RestService controller + Paths paths = generatePathsFromRestService(); + openAPI.setPaths(paths); + + return openAPI; + } + + private Paths generatePathsFromRestService() { + Paths paths = new Paths(); + + try { + // Try to get RestService controller + try { + RestService restService = applicationContext.getBean(RestService.class); + Class> restServiceClass = restService.getClass(); + + // Get all methods with @RequestMapping + Method[] methods = restServiceClass.getDeclaredMethods(); + + for (Method method : methods) { + if (method.isAnnotationPresent(org.springframework.web.bind.annotation.RequestMapping.class)) { + org.springframework.web.bind.annotation.RequestMapping requestMapping = + method.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class); + + // Get the class-level @RequestMapping + org.springframework.web.bind.annotation.RequestMapping classMapping = + restServiceClass.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class); + + // Use the correct base path for CWS UI + String classPath = "/cws-ui/rest"; // Override the class mapping to use correct path + String methodPath = requestMapping.value()[0]; + String fullPath = classPath + methodPath; + + // Create path item + PathItem pathItem = new PathItem(); + + // Determine HTTP method + org.springframework.web.bind.annotation.RequestMethod[] httpMethods = requestMapping.method(); + if (httpMethods.length == 0) { + httpMethods = new org.springframework.web.bind.annotation.RequestMethod[]{org.springframework.web.bind.annotation.RequestMethod.GET}; + } + + // Create operation + Operation operation = new Operation(); + + // Add summary from @Operation annotation if present + if (method.isAnnotationPresent(io.swagger.v3.oas.annotations.Operation.class)) { + io.swagger.v3.oas.annotations.Operation operationAnnotation = + method.getAnnotation(io.swagger.v3.oas.annotations.Operation.class); + operation.setSummary(operationAnnotation.summary()); + } else { + operation.setSummary("REST endpoint: " + method.getName()); + } + + // Extract parameters from method + List parameters = extractParameters(method); + if (!parameters.isEmpty()) { + operation.setParameters(parameters); + } + + // Add responses + ApiResponses methodResponses = new ApiResponses(); + ApiResponse methodSuccessResponse = new ApiResponse() + .description("Successful response") + .content(new Content() + .addMediaType("application/json", new io.swagger.v3.oas.models.media.MediaType() + .schema(new StringSchema()))); + methodResponses.addApiResponse("200", methodSuccessResponse); + operation.setResponses(methodResponses); + + // Add operation to appropriate HTTP method + for (org.springframework.web.bind.annotation.RequestMethod httpMethod : httpMethods) { + switch (httpMethod) { + case GET: + pathItem.setGet(operation); + break; + case POST: + pathItem.setPost(operation); + break; + case PUT: + pathItem.setPut(operation); + break; + case DELETE: + pathItem.setDelete(operation); + break; + } + } + + paths.addPathItem(fullPath, pathItem); + } + } + } catch (Exception e) { + System.err.println("Error discovering RestService endpoints: " + e.getMessage()); + e.printStackTrace(); + } + } catch (Exception e) { + System.err.println("Error generating paths: " + e.getMessage()); + e.printStackTrace(); + } + + return paths; + } + + private List extractParameters(Method method) { + List parameters = new ArrayList<>(); + + try { + java.lang.reflect.Parameter[] methodParams = method.getParameters(); + + for (java.lang.reflect.Parameter param : methodParams) { + // Skip HttpServletRequest, HttpServletResponse, HttpSession, etc. + if (isHttpParameter(param.getType())) { + continue; + } + + Parameter openApiParam = new Parameter(); + + // Check for @RequestParam annotation + if (param.isAnnotationPresent(org.springframework.web.bind.annotation.RequestParam.class)) { + org.springframework.web.bind.annotation.RequestParam requestParam = + param.getAnnotation(org.springframework.web.bind.annotation.RequestParam.class); + + openApiParam.setName(requestParam.value().isEmpty() ? param.getName() : requestParam.value()); + openApiParam.setIn("query"); + openApiParam.setRequired(requestParam.required()); + openApiParam.setSchema(new StringSchema()); + + if (!requestParam.defaultValue().equals(org.springframework.web.bind.annotation.ValueConstants.DEFAULT_NONE)) { + openApiParam.setDescription("Default: " + requestParam.defaultValue()); + } + } + // Check for @PathVariable annotation + else if (param.isAnnotationPresent(org.springframework.web.bind.annotation.PathVariable.class)) { + org.springframework.web.bind.annotation.PathVariable pathVariable = + param.getAnnotation(org.springframework.web.bind.annotation.PathVariable.class); + + openApiParam.setName(pathVariable.value().isEmpty() ? param.getName() : pathVariable.value()); + openApiParam.setIn("path"); + openApiParam.setRequired(true); + openApiParam.setSchema(new StringSchema()); + } + // Check for @RequestBody annotation + else if (param.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class)) { + // RequestBody parameters are handled as request body, not as parameters + continue; + } + // Default to query parameter for other types + else { + openApiParam.setName(param.getName()); + openApiParam.setIn("query"); + openApiParam.setRequired(false); + openApiParam.setSchema(new StringSchema()); + } + + parameters.add(openApiParam); + } + } catch (Exception e) { + System.err.println("Error extracting parameters from method " + method.getName() + ": " + e.getMessage()); + } + + return parameters; + } + + private boolean isHttpParameter(Class> paramType) { + return paramType == jakarta.servlet.http.HttpServletRequest.class || + paramType == jakarta.servlet.http.HttpServletResponse.class || + paramType == jakarta.servlet.http.HttpSession.class || + paramType == org.springframework.web.context.request.WebRequest.class || + paramType == org.springframework.ui.Model.class || + paramType == org.springframework.ui.ModelMap.class; + } +} diff --git a/cws-service/src/main/java/jpl/cws/controller/RestService.java b/cws-service/src/main/java/jpl/cws/controller/RestService.java index 986c5de9..e94c3aa0 100644 --- a/cws-service/src/main/java/jpl/cws/controller/RestService.java +++ b/cws-service/src/main/java/jpl/cws/controller/RestService.java @@ -26,27 +26,29 @@ import java.util.TimeZone; import java.util.UUID; -import javax.jms.BytesMessage; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import com.google.gson.*; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; import org.apache.commons.io.IOUtils; import org.camunda.bpm.engine.ExternalTaskService; import org.camunda.bpm.engine.ManagementService; import org.camunda.bpm.engine.RepositoryService; import org.camunda.bpm.engine.repository.ProcessDefinition; -import org.joda.time.DateTime; +import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -77,7 +79,7 @@ import jpl.cws.scheduler.LogHistory; import jpl.cws.scheduler.Scheduler; import jpl.cws.service.CwsConsoleService; -import springfox.documentation.annotations.ApiIgnore; +import io.swagger.v3.oas.annotations.Hidden; @Controller @RequestMapping("/api") @@ -110,14 +112,16 @@ public class RestService extends MvcCore { @Value("${cws.elasticsearch.username}") private String elasticsearchUsername; @Value("${cws.elasticsearch.password}") private String elasticsearchPassword; - public RestService() {} + public RestService() { + log.info("RestService controller initialized with @RequestMapping('/api')"); + } /** * Gets the contents of the initiators XML context file * */ - @ApiOperation(value="Gets the contents of the initiators XML context file.", tags = {"Initiators"}, produces = "application/xml") + @Operation(summary="Gets the contents of the initiators XML context file.", tags = {"Initiators"}) @RequestMapping(value="/initiators/getXmlContextFile", method=GET) public @ResponseBody String getXmlContextFile() { try { @@ -134,13 +138,10 @@ public RestService() {} * Refreshes initiators from XML file * */ - @ApiOperation(value = "Refreshes initiators from a new XML file.", tags = {"Initiators"}, consumes = "application/xml", produces = "text/plain") - @ApiImplicitParams( - @ApiImplicitParam(name = "newXmlContext", value = "New XML context to update initiators with.", required = true, paramType = "query") - ) + @Operation(summary = "Refreshes initiators from a new XML file.", tags = {"Initiators"}) @RequestMapping(value="/initiators/updateInitiatorsContextXml", method=POST) public @ResponseBody String refreshInitiatorsFromXml(HttpServletResponse response, - @RequestParam("newXmlContext") String newXmlContext) { + @Parameter(description = "New XML context to update initiators with.", required = true, schema = @Schema(type = "string")) @RequestParam("newXmlContext") String newXmlContext) { try { cwsInitiatorsService.updateAndRefreshInitiators(newXmlContext); @@ -157,7 +158,7 @@ public RestService() {} * Refreshes initiators from current working initiators XML file * */ - @ApiOperation(value = "Refreshes initiators from current working initiators XML file.", tags = {"Initiators"}, produces = "text/plain") + @Operation(summary = "Refreshes initiators from current working initiators XML file.", tags = {"Initiators"}) @RequestMapping(value="/initiators/loadInitiatorsContextXml", method=POST) public @ResponseBody String refreshInitiatorsFromXml(HttpServletResponse response) { @@ -177,15 +178,11 @@ public RestService() {} * * Adds or updates single initiator */ - @ApiOperation(value = "Updates a single initiator.", tags = {"Initiators"}, consumes = "application/xml", produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "newXmlContext", value = "New XML context to update initiators with.", required = true, paramType = "query"), - @ApiImplicitParam(name = "beanName", value = "Bean name of the initiator to update.", required = true, paramType = "query") - }) + @Operation(summary = "Updates a single initiator.", tags = {"Initiators"}) @RequestMapping(value="/initiators/updateSingleInitiator", method=POST) public @ResponseBody String updateSingleInitiatorFromXml( - @RequestParam("newXmlContext") String newXmlContext, - @RequestParam("beanName") String beanName) { + @Parameter(description = "New XML context to update initiators with.", required = true, schema = @Schema(type = "string")) @RequestParam("newXmlContext") String newXmlContext, + @Parameter(description = "Bean name of the initiator to update.", required = true, schema = @Schema(type = "string")) @RequestParam("beanName") String beanName) { try { cwsInitiatorsService.updateSingleInitiator(newXmlContext, beanName); @@ -202,13 +199,10 @@ public RestService() {} * * Updates only changed or new initiators */ - @ApiOperation(value = "Updates only changed or new initiators.", tags = {"Initiators"}, consumes = "application/xml", produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "newXmlContext", value = "New XML context to update initiators with.", required = true, paramType = "query") - }) + @Operation(summary = "Updates only changed or new initiators.", tags = {"Initiators"}) @RequestMapping(value="/initiators/updateChangedInitiators", method=POST) public @ResponseBody String updateChangedInitiatorsFromXml( - @RequestParam("newXmlContext") String newXmlContext) { + @Parameter(description = "New XML context to update initiators with.", required = true, schema = @Schema(type = "string")) @RequestParam("newXmlContext") String newXmlContext) { try { cwsInitiatorsService.updateChangedInitiators(newXmlContext); @@ -224,15 +218,11 @@ public RestService() {} * Updates a process initiator's enabled flag. * */ - @ApiOperation(value = "Updates a process initiator's enabled flag.", tags = {"Initiators"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "initiatorId", value = "ID of the initiator to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "enabled", value = "New enabled status of the initiator.", required = true, paramType = "query") - }) + @Operation(summary = "Updates a process initiator's enabled flag.", tags = {"Initiators"}) @RequestMapping(value="/initiators/{initiatorId}/enabled", method=POST) public @ResponseBody ModelAndView setInitiatorEnabled( - @PathVariable String initiatorId, - @RequestParam("enabled") boolean enabled) { + @Parameter(description = "ID of the initiator to update.", required = true, schema = @Schema(type = "string")) @PathVariable String initiatorId, + @Parameter(description = "New enabled status of the initiator.", required = true, schema = @Schema(type = "boolean")) @RequestParam("enabled") boolean enabled) { try { if (enabled) { @@ -251,13 +241,10 @@ public RestService() {} return buildModel("login", "updated initiator enabled to " + enabled); } - @ApiOperation(value = "Enables / disables all process initiators.", tags = {"Initiators"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "enabled", value = "New enabled status of the initiators.", required = true, paramType = "query") - }) + @Operation(summary = "Enables / disables all process initiators.", tags = {"Initiators"}) @RequestMapping(value = "/initiators/all/enabled", method = POST) public @ResponseBody ModelAndView setAllInitiatorsEnabled( - @RequestParam("enabled") boolean enabled) { + @Parameter(description = "New enabled status of the initiators.", required = true, schema = @Schema(type = "boolean")) @RequestParam("enabled") boolean enabled) { try { if (enabled) { @@ -277,13 +264,10 @@ public RestService() {} * Gets a process initiator's enabled flag. * */ - @ApiOperation(value = "Gets a process initiator's enabled flag.", tags = {"Initiators"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "initiatorId", value = "ID of the initiator to get.", required = true, paramType = "path") - }) + @Operation(summary = "Gets a process initiator's enabled flag.", tags = {"Initiators"}) @RequestMapping(value="/initiators/{initiatorId}/enabled", method=GET) public @ResponseBody String isInitiatorEnabled( - @PathVariable String initiatorId) { + @Parameter(description = "ID of the initiator to get.", required = true, schema = @Schema(type = "string")) @PathVariable String initiatorId) { try { log.trace("REST::isInitiatorEnabled isInitiatorEnabled + " + initiatorId); CwsProcessInitiator initiator = @@ -304,8 +288,8 @@ public RestService() {} * Gets all process initiators enabled flag. * */ - @ApiOperation(value = "Gets all process initiators enabled flag.", tags = {"Initiators"}, produces = "application/json") - @RequestMapping(value = "initiators/all/enabled", method = GET) + @Operation(summary = "Gets all process initiators enabled flag.", tags = {"Initiators"}) + @RequestMapping(value = "/initiators/all/enabled", method = GET) public @ResponseBody Map areAllInitiatorsEnabled () { try { log.trace("REST::areAllInitiatorsEnabled"); @@ -332,7 +316,7 @@ public RestService() {} * Returns ModelAndView table body representing the current set of Initiators. * */ - @ApiOperation(value = "Returns ModelAndView table body representing the current set of Initiators.", tags = {"Initiators"}, produces = "text/html") + @Operation(summary = "Returns ModelAndView table body representing the current set of Initiators.", tags = {"Initiators"}) @RequestMapping(value = "/initiators/getInitiatorsHtmlTable", method = GET) public ModelAndView getInitiatorsHtmlTable() { ModelAndView mav = new ModelAndView("initiators-table"); @@ -356,7 +340,7 @@ public ModelAndView getInitiatorsHtmlTable() { * Notify confused User to use POST instead of GET * */ - @ApiOperation(hidden = true, value = "Notify confused User to use POST instead of GET", tags = {"Initiators"}, produces = "text/plain") + @Operation(summary = "Notify confused User to use POST instead of GET", tags = {"Initiators"}, hidden = true) @RequestMapping(value="/deployments/deployProcessDefinition", method = GET) public @ResponseBody String provideDeployProcessDefinitionInfo() { return "You can upload a file by POSTing to this same URL."; @@ -367,15 +351,11 @@ public ModelAndView getInitiatorsHtmlTable() { * Deploys a new process definition from a filename (for deployment from the modeler) * */ - @ApiOperation(value = "Deploys a new process definition from a filename (for deployment from the modeler).", tags = {"Deployments"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "filename", value = "Name of the file to deploy.", required = true, paramType = "query"), - @ApiImplicitParam(name = "xmlData", value = "XML data to deploy.", required = true, paramType = "query") - }) + @Operation(summary = "Deploys a new process definition from a filename (for deployment from the modeler).", tags = {"Deployments"}) @RequestMapping(value="/deployments/deployModelerFile", method = POST) public @ResponseBody String deployModelerFile( - @RequestParam("filename") String filename, - @RequestParam("xmlData") String xmlData) { + @Parameter(description = "Name of the file to deploy.", required = true, schema = @Schema(type = "string")) @RequestParam("filename") String filename, + @Parameter(description = "XML data to deploy.", required = true, schema = @Schema(type = "string")) @RequestParam("xmlData") String xmlData) { // Don't allow filename to contain path modifiers if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) { @@ -423,13 +403,10 @@ public ModelAndView getInitiatorsHtmlTable() { * @throws IOException * */ - @ApiOperation(value = "Deploys a new process definition via a UI-uploaded file.", tags = {"Deployments"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "file", value = "File to deploy.", required = true, paramType = "query") - }) + @Operation(summary = "Deploys a new process definition via a UI-uploaded file.", tags = {"Deployments"}) @RequestMapping(value="/deployments/deployProcessDefinition", method = POST) public @ResponseBody ModelAndView deployProcessDefinition( - @RequestParam("file") MultipartFile file) { + @Parameter(description = "File to deploy.", required = true, schema = @Schema(type = "string", format = "binary")) @RequestParam("file") MultipartFile file) { return buildDeploymentsModel(doDeployProcessDefinition(file)); } @@ -499,13 +476,10 @@ private Boolean elasticsearchUseAuth() { * Undeploys a process definition. * */ - @ApiOperation(value = "Undeploys a process definition.", tags = {"Deployments"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "deploymentId", value = "ID of the deployment to undeploy.", required = true, paramType = "path") - }) + @Operation(summary = "Undeploys a process definition.", tags = {"Deployments"}) @RequestMapping(value = "/processes/processDefinition/{processDefKey}/undeploy", method = GET, produces="application/json") public @ResponseBody String unDeployProcessDefinition( - @PathVariable String processDefKey) { + @Parameter(description = "Key of the process definition to undeploy.", required = true, schema = @Schema(type = "string")) @PathVariable String processDefKey) { try { if (!processService.isProcDefKeyDeployed(processDefKey)) { @@ -554,23 +528,15 @@ private Boolean elasticsearchUseAuth() { return new JsonResponse(JsonResponse.Status.SUCCESS, "Undeployed procDefKey '" + processDefKey + "'").toString(); } - @ApiOperation(value = "Schedules a process definition", tags = {"Deployments"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "processDefKey", value = "Key of the process definition to schedule.", required = true, paramType = "path"), - @ApiImplicitParam(name = "processBusinessKey", value = "Business key of the process to schedule.", required = false, paramType = "query"), - @ApiImplicitParam(name = "initiationKey", value = "Initiation key of the process to schedule.", required = false, paramType = "query"), - @ApiImplicitParam(name = "processPriority", value = "Priority of the process to schedule.", required = false, paramType = "query"), - @ApiImplicitParam(name = "processVariables", value = "Variables of the process to schedule.", required = false, paramType = "query") - - }) + @Operation(summary = "Schedules a process definition", tags = {"Deployments"}) @RequestMapping(value = "/process/{processDefKey}/schedule", method = POST) public @ResponseBody String scheduleProcess( - @ApiIgnore final HttpSession session, - @PathVariable String processDefKey, - @RequestParam (value = "processBusinessKey", required=false) String processBusinessKey, - @RequestParam (value = "initiationKey", required=false) String initiationKey, - @RequestParam (value = "processPriority", required=false, defaultValue="default") String processPriority, - @RequestParam MultiValueMap processVariables + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "Key of the process definition to schedule.", required = true, schema = @Schema(type = "string")) @PathVariable String processDefKey, + @Parameter(description = "Business key of the process to schedule.", required = false, schema = @Schema(type = "string")) @RequestParam (value = "processBusinessKey", required=false) String processBusinessKey, + @Parameter(description = "Initiation key of the process to schedule.", required = false, schema = @Schema(type = "string")) @RequestParam (value = "initiationKey", required=false) String initiationKey, + @Parameter(description = "Priority of the process to schedule.", required = false, schema = @Schema(type = "string")) @RequestParam (value = "processPriority", required=false, defaultValue="default") String processPriority, + @Parameter(description = "Variables of the process to schedule.", required = false, schema = @Schema(implementation = MultiValueMap.class)) @RequestParam MultiValueMap processVariables ) { log.info("******* REST (POST) SCHEDULING Process '" + processDefKey + "' " + @@ -611,7 +577,7 @@ private Boolean elasticsearchUseAuth() { schedulerJob = scheduler.scheduleProcess(processDefKey, procVariablesMap, processBusinessKey, initiationKey, priority); } catch (Exception e) { log.error("FAILED TO SCHEDULE PROCESS: "+processDefKey); - Timestamp tsNow = new Timestamp(DateTime.now().getMillis()); + Timestamp tsNow = Timestamp.from(Instant.now()); schedulerJob = new SchedulerJob(null, tsNow, tsNow, null, null, processDefKey, priority, procVariablesMap, processBusinessKey, initiationKey, "failedToSchedule", e.getMessage()); return new GsonBuilder().setPrettyPrinting().create().toJson(schedulerJob); @@ -624,14 +590,11 @@ private Boolean elasticsearchUseAuth() { * REST method used to get status information about a process instance * */ - @ApiOperation(value = "Gets status information about a process instance.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "uuid", value = "UUID of the process instance to get status for.", required = true, paramType = "path") - }) + @Operation(summary = "Gets status information about a process instance.", tags = {"Processes"}) @RequestMapping(value = "/process-instance/{uuid}/status", method = GET, produces="application/json") public @ResponseBody String getProcessInstanceStatus( - @PathVariable String uuid, - @ApiIgnore final HttpSession session) { + @Parameter(description = "UUID of the process instance to get status for.", required = true, schema = @Schema(type = "string")) @PathVariable String uuid, + @Parameter(hidden = true) final HttpSession session) { log.debug("REST: getProcessInstanceStatus(" + uuid + ")"); @@ -646,16 +609,12 @@ private Boolean elasticsearchUseAuth() { /** * Returns status counts for (proc_def_key, business_key) pair */ - @ApiOperation(value = "Gets status counts for (proc_def_key, business_key) pair.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "businessKey", value = "Business key of the process instance to get status for.", required = true, paramType = "query"), - @ApiImplicitParam(name = "procDefKey", value = "Process definition key of the process instance to get status for.", required = true, paramType = "query") - }) + @Operation(summary = "Gets status counts for (proc_def_key, business_key) pair.", tags = {"Processes"}) @RequestMapping(value="/stats/statsByBusinessKey", method = GET) - public @ResponseBody Map statsByBusinessKey( - @RequestParam(value = "businessKey", required=true) String businessKey, - @RequestParam(value = "procDefKey", required=true) String procDefKey - ) { + public @ResponseBody Map statsByBusinessKey( + @Parameter(description = "Business key of the process instance to get status for.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "businessKey", required=true) String businessKey, + @Parameter(description = "Process definition key of the process instance to get status for.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "procDefKey", required=true) String procDefKey + ) { Map ret = new HashMap<>(); try { @@ -671,7 +630,7 @@ private Boolean elasticsearchUseAuth() { /** * Returns latest successfully compiled code snippet from DB */ - @ApiOperation(value = "Gets latest successfully compiled code snippet from DB.", tags = {"Snippets"}, produces = "text/plain") + @Operation(summary = "Gets latest successfully compiled code snippet from DB.", tags = {"Snippets"}) @RequestMapping(value="/snippets/getLatestCodeSnippet", method = GET) public @ResponseBody String getLatestCodeSnippet() { return cwsConsoleService.getLatestCode(); @@ -681,7 +640,7 @@ private Boolean elasticsearchUseAuth() { /** * Returns latest code snippet from DB */ - @ApiOperation(value = "Gets latest code snippet from DB.", tags = {"Snippets"}, produces = "text/plain") + @Operation(summary = "Gets latest code snippet from DB.", tags = {"Snippets"}) @RequestMapping(value="/snippets/getLatestInProgressCodeSnippet", method = GET) public @ResponseBody String getLatestInProgressCodeSnippet() { return cwsConsoleService.getLatestInProgressCode(); @@ -691,14 +650,11 @@ private Boolean elasticsearchUseAuth() { /** * Saves UI-edited code to the database. */ - @ApiOperation(value = "Saves UI-edited code snippet to the database.", tags = {"Snippets"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "code", value = "Code to save.", required = true, paramType = "query") - }) + @Operation(summary = "Saves UI-edited code snippet to the database.", tags = {"Snippets"}) @RequestMapping(value = "/snippets/validateAndSaveSnippets", method = POST) public ModelAndView validateAndSaveSnippets( - @RequestParam String code, - @ApiIgnore final HttpSession session) { + @Parameter(description = "Code to save.", required = true, schema = @Schema(type = "string")) @RequestParam String code, + @Parameter(hidden = true) final HttpSession session) { log.debug("REST: validateAndSaveSnippets"); log.trace("REST: validateAndSaveSnippets, code=" + code); @@ -719,7 +675,7 @@ public ModelAndView validateAndSaveSnippets( /** * Sends a message to shutdown the entire system, including all remote workers */ - @ApiOperation(value = "Sends a message to shutdown the entire system, including all remote workers.", tags = {"System"}, produces = "text/plain") + @Operation(summary = "Sends a message to shutdown the entire system, including all remote workers.", tags = {"System"}) @RequestMapping(value="/system/shutdown", method = GET) public @ResponseBody String doSystemShutdown() { return cwsConsoleService.doSystemShutdown(); @@ -730,13 +686,10 @@ public ModelAndView validateAndSaveSnippets( * REST method used to get logs * */ - @ApiOperation(value = "Gets logs using a scroll ID to keep track of already fetched data. Used on logs page.", tags = {"Logs"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "scrollId", value = "Scroll ID to keep track of already fetched data.", required = true, paramType = "query") - }) + @Operation(summary = "Gets logs using a scroll ID to keep track of already fetched data. Used on logs page.", tags = {"Logs"}) @RequestMapping(value = "/logs/get/scroll", method = POST, produces="application/json") public @ResponseBody String getLogsScroll( - @RequestParam(value = "scrollId") String scrollId) { + @Parameter(description = "Scroll ID to keep track of already fetched data.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "scrollId") String scrollId) { String urlString = constructElasticsearchUrl("/_search/scroll"); String jsonData = "{ \"scroll\" : \"1m\", \"scroll_id\" : \"" + scrollId + "\" }"; @@ -767,7 +720,7 @@ public ModelAndView validateAndSaveSnippets( * REST method used to get the total number of log rows * */ - @ApiOperation(value = "Gets the total number of log rows.", tags = {"Logs"}, produces = "application/json") + @Operation(summary = "Gets the total number of log rows.", tags = {"Logs"}) @RequestMapping(value="/logs/get/count", method = GET, produces="application/json") public @ResponseBody String getNumLogs() { String urlString = constructElasticsearchUrl("/" + elasticsearchIndexPrefix + "-logstash-*/_count"); @@ -797,13 +750,10 @@ public ModelAndView validateAndSaveSnippets( * REST method used to get logs on the logs page (shorter scroll timer) * */ - @ApiOperation(value = "Gets logs on the logs page (shorter scroll timer).", tags = {"Logs"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "source", value = "Source of the logs to get.", required = true, paramType = "query") - }) + @Operation(summary = "Gets logs on the logs page (shorter scroll timer).", tags = {"Logs"}) @RequestMapping(value = "/logs/get/noScroll", method = GET, produces="application/json") public @ResponseBody String getLogsNoScroll( - @RequestParam(value = "source") String source) { + @Parameter(description = "Source of the logs to get.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "source") String source) { String urlString = constructElasticsearchUrl("/" + elasticsearchIndexPrefix + "-logstash-*/_search"); log.debug("REST logs/get/noScroll query = " + urlString); @@ -835,13 +785,10 @@ public ModelAndView validateAndSaveSnippets( * REST method used to get logs * */ - @ApiOperation(value = "Gets logs.", tags = {"Logs"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "source", value = "Source of the logs to get.", required = true, paramType = "query") - }) + @Operation(summary = "Gets logs.", tags = {"Logs"}) @RequestMapping(value = "/logs/get", method = GET, produces="application/json") public @ResponseBody String getLogs( - @RequestParam(value = "source") String source) { + @Parameter(description = "Source of the logs to get.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "source") String source) { String urlString = constructElasticsearchUrl("/" + elasticsearchIndexPrefix + "-logstash-*/_search?scroll=5m&source=" + source + "&source_content_type=application/json"); log.trace("REST getLogs query = " + urlString); @@ -870,14 +817,11 @@ public ModelAndView validateAndSaveSnippets( * REST method used to delete logs by procDefKey * */ - @ApiOperation(value = "Deletes logs by procDefKey.", tags = {"Logs"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procDefKey", value = "Process definition key to delete logs for.", required = true, paramType = "path") - }) + @Operation(summary = "Deletes logs by procDefKey.", tags = {"Logs"}) @RequestMapping(value = "/logs/delete/{procDefKey}", method = DELETE, produces="application/json") public @ResponseBody String deleteLogsByProcDefKey( HttpServletResponse response, - @PathVariable String procDefKey + @Parameter(description = "Process definition key to delete logs for.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefKey ) { String urlString = constructElasticsearchUrl("/" + elasticsearchIndexPrefix + "-logstash*/_delete_by_query"); log.debug("REST deleteLogsByProcDefKey url = " + urlString); @@ -944,12 +888,9 @@ public GsonUTCDateAdapter() { * REST method used to get history (logs + historical data) * */ - @ApiOperation(value = "Gets history (logs + historical data).", tags = {"History"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procInstId", value = "Process instance ID to get history for.", required = true, paramType = "path") - }) + @Operation(summary = "Gets history (logs + historical data).", tags = {"History"}) @RequestMapping(value = "/history/{procInstId}", method = GET, produces="application/json") - public @ResponseBody String getHistory(@PathVariable String procInstId) { + public @ResponseBody String getHistory(@Parameter(description = "Process instance ID to get history for.", required = true, schema = @Schema(type = "string")) @PathVariable String procInstId) { LogHistory history = cwsConsoleService.getHistoryForProcess(procInstId); @@ -963,7 +904,7 @@ public GsonUTCDateAdapter() { * REST method used to get Elasticsearch stats * */ - @ApiOperation(value = "Gets Elasticsearch stats.", tags = {"Elasticsearch"}, produces = "application/json") + @Operation(summary = "Gets Elasticsearch stats.", tags = {"Elasticsearch"}) @RequestMapping(value = "/stats/es/indices", method = GET, produces="application/json") public @ResponseBody String getElasticsearchIndices() { String urlString = constructElasticsearchUrl("/_cat/indices?v&bytes=b&s=index&format=json"); @@ -995,7 +936,7 @@ public GsonUTCDateAdapter() { * REST method used to get Elasticsearch stats * */ - @ApiOperation(value = "Gets Elasticsearch cluster health.", tags = {"Elasticsearch"}, produces = "application/json") + @Operation(summary = "Gets Elasticsearch cluster health.", tags = {"Elasticsearch"}) @RequestMapping(value = "/stats/es/cluster/health", method = GET, produces="application/json") public @ResponseBody String getElasticsearchClusterHealth() { String urlString = constructElasticsearchUrl("/_cluster/health"); @@ -1027,7 +968,7 @@ public GsonUTCDateAdapter() { * REST method used to get Elasticsearch stats * */ - @ApiOperation(value = "Gets Elasticsearch stats.", tags = {"Elasticsearch"}, produces = "application/json") + @Operation(summary = "Gets Elasticsearch stats.", tags = {"Elasticsearch"}) @RequestMapping(value = "/stats/es", method = GET, produces="application/json") public @ResponseBody String getElasticsearchStats() { String urlString = constructElasticsearchUrl("/_nodes/stats/_all"); @@ -1058,7 +999,7 @@ public GsonUTCDateAdapter() { /** * Returns latest system stats (Db size, ES size, Disk space, Log sizes, etc... */ - @ApiOperation(value = "Gets system stats.", tags = {"System"}, produces = "application/json") + @Operation(summary = "Gets system stats.", tags = {"System"}) @RequestMapping(value="/stats/diskUsage", method = GET, produces = "application/json") public @ResponseBody String getDiskStats(HttpServletResponse response) { @@ -1082,16 +1023,14 @@ public GsonUTCDateAdapter() { } - /** - * Returns latest code snippet from DB - */ - @ApiOperation(value = "Gets latest code snippet from DB.", tags = {"Snippets"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "snippetId", value = "ID of the snippet to get.", required = false, paramType = "query") - }) + /* + * Return JSON key values of process status + * e.g. {PD1: {errors:4, pending:3,... },...} + */ + @Operation(summary = "Gets process instance stats.", tags = {"Processes"}) @RequestMapping(value="/stats/processInstanceStats", method = GET) public @ResponseBody Map getProcessInstanceStats( - @RequestParam(value = "lastNumHours", required=false) String lastNumHours + @Parameter(description = "Number of hours to get stats for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "lastNumHours", required=false) String lastNumHours ) { return cwsConsoleService.getProcessInstanceStats(lastNumHours); @@ -1102,13 +1041,10 @@ public GsonUTCDateAdapter() { * Return JSON key values of process status * e.g. {PD1: {errors:4, pending:3,... },...} */ - @ApiOperation(value = "Gets process instance stats (JSON).", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "lastNumHours", value = "Number of hours to get stats for.", required = false, paramType = "query") - }) + @Operation(summary = "Gets process instance stats (JSON).", tags = {"Processes"}) @RequestMapping(value="/stats/processInstanceStatsJSON", method = GET) public @ResponseBody Map> getProcessInstanceStatsJSON( - @RequestParam(value = "lastNumHours", required=false) String lastNumHours + @Parameter(description = "Number of hours to get stats for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "lastNumHours", required=false) String lastNumHours ) { Map> ret = new HashMap>(); try { @@ -1126,7 +1062,7 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Gets pending process instances (JSON).", tags = {"Processes"}, produces = "application/json") + @Operation(summary = "Gets pending process instances (JSON).", tags = {"Processes"}) @RequestMapping(value="/stats/pendingProcessesJSON", method = GET, produces="application/json") public @ResponseBody String getPendingProcessesJSON(HttpServletResponse response) { JsonArray json = new JsonArray(); @@ -1152,7 +1088,7 @@ public GsonUTCDateAdapter() { * * FIXME: This can result in double-counting (e.g. a running task has an external task as well) */ - @ApiOperation(value = "Gets number of running processes for each worker.", tags = {"Workers"}, produces = "application/json") + @Operation(summary = "Gets number of running processes for each worker.", tags = {"Workers"}) @RequestMapping(value="/stats/workerNumRunningProcs", method = GET) public @ResponseBody Map getWorkerNumRunningProcs() { @@ -1178,20 +1114,14 @@ public GsonUTCDateAdapter() { * * FIXME: remove processVariables parameter below -- I don't think it's used */ - @ApiOperation(value = "Update the number of process definitions a worker can be working on at any given time.", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "procDefKey", value = "Key of the process definition to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "newLimit", value = "New limit for the worker.", required = true, paramType = "path"), - @ApiImplicitParam(name = "processVariables", value = "Process variables to update.", required = false, paramType = "query") - }) + @Operation(summary = "Update the number of process definitions a worker can be working on at any given time.", tags = {"Workers"}) @RequestMapping(value = "/worker/{workerId}/{procDefKey}/updateWorkerProcDefLimit/{newLimit}", method = POST) public @ResponseBody String updateWorkerProcDefLimit( - @ApiIgnore final HttpSession session, - @PathVariable String workerId, - @PathVariable String procDefKey, - @PathVariable String newLimit, - @RequestParam MultiValueMap processVariables) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId, + @Parameter(description = "Key of the process definition to update.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefKey, + @Parameter(description = "New limit for the worker.", required = true, schema = @Schema(type = "string")) @PathVariable String newLimit, + @Parameter(description = "Process variables to update.", required = false, schema = @Schema(implementation = MultiValueMap.class)) @RequestParam MultiValueMap processVariables) { log.info("*** REST CALL *** updateWorkerProcDefLimit (workerId='"+workerId+"', procDefKey='"+procDefKey+"', newLimit='"+newLimit+"')..."); @@ -1215,18 +1145,13 @@ public GsonUTCDateAdapter() { * Inserts or updates worker tag with name and value * */ - @ApiOperation(value = "Inserts or updates worker tag with name and value.", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "name", value = "Name of the tag to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "value", value = "Value of the tag to update.", required = true, paramType = "query") - }) + @Operation(summary = "Inserts or updates worker tag with name and value.", tags = {"Workers"}) @RequestMapping(value = "/worker/{workerId}/updateTag/{name}", method = POST, produces="application/json") public @ResponseBody String updateWorkerTag( HttpServletResponse response, - @PathVariable String workerId, - @PathVariable String name, - @RequestParam(value = "value") String value) { + @Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId, + @Parameter(description = "Name of the tag to update.", required = true, schema = @Schema(type = "string")) @PathVariable String name, + @Parameter(description = "Value of the tag to update.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "value") String value) { try { dbService.updateWorkerTag(workerId, name, value); @@ -1248,13 +1173,10 @@ public GsonUTCDateAdapter() { * Checks if procDefKey is deployed (exists) * */ - @ApiOperation(value = "Checks if process definition key is deployed.", tags = {"Processes"}, produces = "text/plain") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procDefKey", value = "Key of the process definition to check.", required = true, paramType = "query") - }) + @Operation(summary = "Checks if process definition key is deployed.", tags = {"Processes"}) @RequestMapping(value = "/isProcDefKeyDeployed", method = POST) public @ResponseBody String isProcDefKeyDeployed( - @RequestParam(value = "procDefKey", required=true) String procDefKey) { + @Parameter(description = "Key of the process definition to check.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "procDefKey", required=true) String procDefKey) { log.trace("isProcDefKeyDeployed... (procDefKey="+procDefKey+")"); @@ -1269,9 +1191,9 @@ public GsonUTCDateAdapter() { /** * Get list of all workers with active status for the process */ - @ApiOperation(value = "Gets list of all workers with active status for the process.", tags = {"Workers", "Processes"}, produces = "application/json") + @Operation(summary = "Gets list of all workers with active status for the process.", tags = {"Workers", "Processes"}) @RequestMapping(value="/worker/{procDefKey}/getWorkersForProc", method = GET) - public @ResponseBody String getWorkersForProc(@PathVariable String procDefKey) { + public @ResponseBody String getWorkersForProc(@Parameter(description = "Process definition key to get workers for.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefKey) { List> procWorkers = dbService.getWorkersForProcDefKey(procDefKey); @@ -1282,13 +1204,10 @@ public GsonUTCDateAdapter() { /** * Add new external worker */ - @ApiOperation(value = "Adds new external worker.", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "hostname", value = "Hostname of the worker to add.", required = true, paramType = "query") - }) + @Operation(summary = "Adds new external worker.", tags = {"Workers"}) @RequestMapping(value="/externalWorker/add", method = GET) public @ResponseBody String addExternalWorker( - @RequestParam(value = "hostname") String hostname) { + @Parameter(description = "Hostname of the worker to add.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "hostname") String hostname) { String workerId = UUID.randomUUID().toString(); String workerName = dbService.createExternalWorkerRow(workerId, hostname); @@ -1304,31 +1223,21 @@ public GsonUTCDateAdapter() { /** * Update external worker heartbeat */ - @ApiOperation(value = "Updates external worker heartbeat.", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path") - }) + @Operation(summary = "Updates external worker heartbeat.", tags = {"Workers"}) @RequestMapping(value="/externalWorker/{workerId}/heartbeat", method = GET) - public @ResponseBody void externalWorkerHeartbeat(@PathVariable String workerId) { + public @ResponseBody void externalWorkerHeartbeat(@Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId) { dbService.updateExternalWorkerHeartbeat(workerId); } - @ApiOperation(value = "Updates external worker.", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "activeTopics", value = "Active topics of the worker to update.", required = false, paramType = "query"), - @ApiImplicitParam(name = "currentTopic", value = "Current topic of the worker to update.", required = false, paramType = "query"), - @ApiImplicitParam(name = "currentCommand", value = "Current command of the worker to update.", required = false, paramType = "query"), - @ApiImplicitParam(name = "currentWorkingDir", value = "Current working directory of the worker to update.", required = false, paramType = "query") - }) + @Operation(summary = "Updates external worker.", tags = {"Workers"}) @RequestMapping(value = "/externalWorker/{workerId}/update", method = POST) public @ResponseBody String updateExternalWorker( - @PathVariable String workerId, - @RequestParam(value = "activeTopics", required=false) String activeTopics, - @RequestParam(value = "currentTopic", required=false) String currentTopic, - @RequestParam(value = "currentCommand", required=false) String currentCommand, - @RequestParam(value = "currentWorkingDir", required=false) String currentWorkingDir) { + @Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId, + @Parameter(description = "Active topics of the worker to update.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "activeTopics", required=false) String activeTopics, + @Parameter(description = "Current topic of the worker to update.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "currentTopic", required=false) String currentTopic, + @Parameter(description = "Current command of the worker to update.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "currentCommand", required=false) String currentCommand, + @Parameter(description = "Current working directory of the worker to update.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "currentWorkingDir", required=false) String currentWorkingDir) { try { if (activeTopics != null) { @@ -1358,24 +1267,15 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Gets the size of an instance", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "superProcInstId", value = "Super process instance ID to get size for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "procInstId", value = "Process instance ID to get size for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "procDefKey", value = "Process definition key to get size for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "status", value = "Status to get size for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "minDate", value = "Minimum date to get size for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "maxDate", value = "Maximum date to get size for.", required = false, paramType = "query") - // Removed maxReturn parameter - not needed with server-side pagination - }) + @Operation(summary = "Gets the size of an instance", tags = {"Processes"}) @RequestMapping(value = "/processes/getInstancesSize", method = GET, produces="application/json") public @ResponseBody int getInstancesSize( - @RequestParam(value = "superProcInstId", required=false) String superProcInstId, - @RequestParam(value = "procInstId", required=false) String procInstId, - @RequestParam(value = "procDefKey", required=false) String procDefKey, - @RequestParam(value = "status", required=false) String status, - @RequestParam(value = "minDate", required=false) String minDate, - @RequestParam(value = "maxDate", required=false) String maxDate + @Parameter(description = "Super process instance ID to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "superProcInstId", required=false) String superProcInstId, + @Parameter(description = "Process instance ID to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "procInstId", required=false) String procInstId, + @Parameter(description = "Process definition key to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "procDefKey", required=false) String procDefKey, + @Parameter(description = "Status to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "status", required=false) String status, + @Parameter(description = "Minimum date to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "minDate", required=false) String minDate, + @Parameter(description = "Maximum date to get size for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "maxDate", required=false) String maxDate // Removed maxReturn parameter - not needed with server-side pagination ) { @@ -1397,13 +1297,10 @@ public GsonUTCDateAdapter() { return size; } - @ApiOperation(value = "Gets the status of a process isntance ID", tags = {"Processes", "History"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procInstId", value = "Process instance ID to get status for.", required = true, paramType = "path") - }) + @Operation(summary = "Gets the status of a process instance ID", tags = {"Processes", "History"}) @RequestMapping(value="/history/getStatus/{procInstId}", method = GET) public @ResponseBody String getStatusByProcInstId( - @PathVariable String procInstId) { + @Parameter(description = "Process instance ID to get status for.", required = true, schema = @Schema(type = "string")) @PathVariable String procInstId) { List instances = null; instances = cwsConsoleService.getFilteredProcessInstancesCamunda( null, procInstId, null, null, null, null, "DESC", 0); @@ -1418,34 +1315,23 @@ public GsonUTCDateAdapter() { * REST method used to get Processes table JSON * */ - @ApiOperation(value = "Gets camunda instances.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "superProcInstId", value = "Super process instance ID to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "procInstId", value = "Process instance ID to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "procDefKey", value = "Process definition key to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "status", value = "Status to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "minDate", value = "Minimum date to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "maxDate", value = "Maximum date to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "dateOrderBy", value = "Date order by to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "page", value = "Page to get instances for.", required = false, paramType = "query"), - @ApiImplicitParam(name = "maxReturn", value = "Maximum number of results to return.", required = false, paramType = "query") - }) + @Operation(summary = "Gets camunda instances.", tags = {"Processes"}) @RequestMapping(value = "/processes/getInstancesCamunda", method = GET, produces="application/json") public @ResponseBody String getProcessInstancesCamunda( - @RequestParam(value = "superProcInstId", required=false) String superProcInstId, - @RequestParam(value = "procInstId", required=false) String procInstId, - @RequestParam(value = "procDefKey", required=false) String procDefKey, - @RequestParam(value = "status", required=false) String status, - @RequestParam(value = "minDate", required=false) String minDate, - @RequestParam(value = "maxDate", required=false) String maxDate, - @RequestParam(value = "dateOrderBy", required=false, defaultValue="DESC") String dateOrderBy, - @RequestParam(value = "page", required=false, defaultValue="0") String page, - @RequestParam(value = "pageSize", required=false, defaultValue="50") String pageSize, - @RequestParam(value = "start", required=false) String start, - @RequestParam(value = "length", required=false) String length, - @RequestParam(value = "draw", required=false) String draw, - @RequestParam(value = "maxReturn", required=false, defaultValue="-1") String maxReturn, - @RequestParam Map allRequestParams // Handle SearchBuilder + @Parameter(description = "Super process instance ID to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "superProcInstId", required=false) String superProcInstId, + @Parameter(description = "Process instance ID to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "procInstId", required=false) String procInstId, + @Parameter(description = "Process definition key to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "procDefKey", required=false) String procDefKey, + @Parameter(description = "Status to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "status", required=false) String status, + @Parameter(description = "Minimum date to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "minDate", required=false) String minDate, + @Parameter(description = "Maximum date to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "maxDate", required=false) String maxDate, + @Parameter(description = "Date order by to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "dateOrderBy", required=false, defaultValue="DESC") String dateOrderBy, + @Parameter(description = "Page to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "page", required=false, defaultValue="0") String page, + @Parameter(description = "Page size to get instances for.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pageSize", required=false, defaultValue="50") String pageSize, + @Parameter(description = "Start index for DataTables pagination.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "start", required=false) String start, + @Parameter(description = "Length for DataTables pagination.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "length", required=false) String length, + @Parameter(description = "Draw counter for DataTables.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "draw", required=false) String draw, + @Parameter(description = "Maximum number of results to return.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "maxReturn", required=false, defaultValue="-1") String maxReturn, + @Parameter(description = "Additional request parameters for SearchBuilder.", required = false, schema = @Schema(implementation = Map.class)) @RequestParam Map allRequestParams // Handle SearchBuilder ) { List instances = null; @@ -1529,7 +1415,7 @@ public GsonUTCDateAdapter() { /** * List of all process definitions and number of workers selected for each */ - @ApiOperation(value = "Gets process definitions and number of workers selected for each.", tags = {"Processes"}, produces = "application/json") + @Operation(summary = "Gets process definitions and number of workers selected for each.", tags = {"Processes"}) @RequestMapping(value="/processes/getProcDefWorkerCount", method = GET) public @ResponseBody String getProcDefWorkerCount() { @@ -1543,14 +1429,11 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Makes disabled processes pending", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "uuids", value = "UUIDs of the processes to make pending.", required = true, paramType = "body") - }) + @Operation(summary = "Makes disabled processes pending", tags = {"Processes"}) @RequestMapping(value = "/processes/makeDisabledRowsPending", method = POST) public @ResponseBody String makeDisabledRowsPending( - @ApiIgnore final HttpSession session, - @RequestBody List uuids) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "UUIDs of the processes to make pending.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List uuids) { log.info("*** REST CALL *** /processes/makeDisabledRowsPending ... " + uuids.size()); @@ -1571,14 +1454,11 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Makes pending processes disabled", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "uuids", value = "UUIDs of the processes to make disabled.", required = true, paramType = "body") - }) + @Operation(summary = "Makes pending processes disabled", tags = {"Processes"}) @RequestMapping(value = "/processes/makePendingRowsDisabled", method = POST) public @ResponseBody String makePendingRowsDisabled( - @ApiIgnore final HttpSession session, - @RequestBody List uuids) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "UUIDs of the processes to make disabled.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List uuids) { log.info("*** REST CALL *** /processes/makePendingRowsDisabled ... " + uuids.size()); @@ -1598,16 +1478,12 @@ public GsonUTCDateAdapter() { * Retry incidents * */ - @ApiOperation(value = "Retries processes that have the status 'Incident'.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "retries", value = "Number of retries to set for the incidents.", required = true, paramType = "query"), - @ApiImplicitParam(name = "uuids", value = "UUIDs of the processes to retry.", required = true, paramType = "body") - }) + @Operation(summary = "Retries processes that have the status 'Incident'.", tags = {"Processes"}) @RequestMapping(value = "/processes/retryIncidentRows", method = POST) public @ResponseBody ResponseEntity retryIncidentRows( - @ApiIgnore final HttpSession session, - @RequestParam(defaultValue = "1") String retries, - @RequestBody List uuids) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "Number of retries to set for the incidents.", required = false, schema = @Schema(type = "string")) @RequestParam(defaultValue = "1") String retries, + @Parameter(description = "UUIDs of the processes to retry.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List uuids) { log.info("*** REST CALL *** /processes/retryIncidentRows ... " + uuids.size()); @@ -1636,14 +1512,11 @@ public GsonUTCDateAdapter() { * Retry failedToStart * */ - @ApiOperation(value = "Retries processes that have the status 'Failed to Start'.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "uuids", value = "UUIDs of the processes to retry.", required = true, paramType = "body") - }) + @Operation(summary = "Retries processes that have the status 'Failed to Start'.", tags = {"Processes"}) @RequestMapping(value = "/processes/retryFailedToStart", method = POST) public @ResponseBody ResponseEntity retryFailedToStart( - @ApiIgnore final HttpSession session, - @RequestBody List uuids) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "UUIDs of the processes to retry.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List uuids) { log.info("*** REST CALL *** /processes/retryFailedToStart ... " + uuids.size()); @@ -1661,14 +1534,11 @@ public GsonUTCDateAdapter() { * Mark 'fail' as resolved * */ - @ApiOperation(value = "Marks processes that have the status 'Fail' as resolved.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "uuids", value = "UUIDs of the processes to mark as resolved.", required = true, paramType = "body") - }) + @Operation(summary = "Marks processes that have the status 'Fail' as resolved.", tags = {"Processes"}) @RequestMapping(value = "/processes/markResolved", method = POST) public @ResponseBody ResponseEntity markResolved( - @ApiIgnore final HttpSession session, - @RequestBody List procInstIds) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "UUIDs of the processes to mark as resolved.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List procInstIds) { log.info("*** REST CALL *** /processes/markResolved ... " + procInstIds.size()); @@ -1686,20 +1556,14 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Updates the enabled/disabled status of a process definition on a worker", tags = {"Processes", "Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "procDefKey", value = "Key of the process definition to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "enabledFlag", value = "Flag to set the process definition to.", required = true, paramType = "path"), - @ApiImplicitParam(name = "processVariables", value = "Process variables to update.", required = false, paramType = "query") - }) + @Operation(summary = "Updates the enabled/disabled status of a process definition on a worker", tags = {"Processes", "Workers"}) @RequestMapping(value = "/worker/{workerId}/{procDefKey}/updateWorkerProcDefEnabled/{enabledFlag}", method = POST) public @ResponseBody String updateWorkerProcDefEnabled( - @ApiIgnore final HttpSession session, - @PathVariable String workerId, - @PathVariable String procDefKey, - @PathVariable String enabledFlag, - @RequestParam MultiValueMap processVariables) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId, + @Parameter(description = "Key of the process definition to update.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefKey, + @Parameter(description = "Flag to set the process definition to.", required = true, schema = @Schema(type = "string")) @PathVariable String enabledFlag, + @Parameter(description = "Process variables to update.", required = false, schema = @Schema(implementation = MultiValueMap.class)) @RequestParam MultiValueMap processVariables) { log.info("*** REST CALL *** updateWorkerProcDefEnabled (workerId='"+workerId+"', procDefKey='"+procDefKey+"', enabledFlag='"+enabledFlag+"')..."); @@ -1721,13 +1585,10 @@ public GsonUTCDateAdapter() { * Suspends a process definition given its procDefId * */ - @ApiOperation(value = "Suspends a process definition given its procDefId.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procDefId", value = "ID of the process definition to suspend.", required = true, paramType = "path") - }) + @Operation(summary = "Suspends a process definition given its procDefId.", tags = {"Processes"}) @RequestMapping(value = "/deployments/suspend/{procDefId}", method = POST) public @ResponseBody String suspendProcDefId( - @PathVariable String procDefId) { + @Parameter(description = "ID of the process definition to suspend.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefId) { log.info("*** REST CALL *** suspendProcDefId (procDefId=" + procDefId + ")"); String result = cwsConsoleService.suspendProcDefId(procDefId); return result; @@ -1737,13 +1598,10 @@ public GsonUTCDateAdapter() { * Activates a suspended process definition given its procDefId * */ - @ApiOperation(value = "Activates a suspended process definition given its procDefId.", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procDefId", value = "ID of the process definition to activate.", required = true, paramType = "path") - }) + @Operation(summary = "Activates a suspended process definition given its procDefId.", tags = {"Processes"}) @RequestMapping(value = "/deployments/activate/{procDefId}", method = POST) public @ResponseBody String activateProcDefId( - @PathVariable String procDefId ) { + @Parameter(description = "ID of the process definition to activate.", required = true, schema = @Schema(type = "string")) @PathVariable String procDefId ) { log.info ("*** REST CALL *** activateProcDefId (procDefId" + procDefId + ")"); String result = cwsConsoleService.activateProcDefId(procDefId); return result; @@ -1754,14 +1612,11 @@ public GsonUTCDateAdapter() { * * Accepts an array of procInstIds and expects all of them to be running. */ - @ApiOperation(value = "Deletes running process instances (Only pass running instances into this endpoint)", tags = {"Processes"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "procInstIds", value = "IDs of the process instances to delete. Expects all of the process instances in the list to be running.", required = true, paramType = "body") - }) + @Operation(summary = "Deletes running process instances (Only pass running instances into this endpoint)", tags = {"Processes"}) @RequestMapping(value = "/processes/delete", method = POST) public @ResponseBody String deleteRunningProcInsts( - @ApiIgnore final HttpSession session, - @RequestBody List procInstIds) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "IDs of the process instances to delete. Expects all of the process instances in the list to be running.", required = true, schema = @Schema(implementation = List.class)) @RequestBody List procInstIds) { log.debug("*** REST CALL *** deleteRunningProcInsts"); String result = cwsConsoleService.deleteRunningProcInst(procInstIds); return result; @@ -1771,16 +1626,12 @@ public GsonUTCDateAdapter() { * * */ - @ApiOperation(value = "Updates the number of job executor threads for a worker", tags = {"Workers"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "workerId", value = "ID of the worker to update.", required = true, paramType = "path"), - @ApiImplicitParam(name = "numThreads", value = "Number of threads to set for the worker.", required = true, paramType = "path") - }) + @Operation(summary = "Updates the number of job executor threads for a worker", tags = {"Workers"}) @RequestMapping(value = "/worker/{workerId}/updateNumJobExecThreads/{numThreads}", method = POST) public @ResponseBody String updateWorkerNumJobExecThreads( - @ApiIgnore final HttpSession session, - @PathVariable String workerId, - @PathVariable String numThreads) { + @Parameter(hidden = true) final HttpSession session, + @Parameter(description = "ID of the worker to update.", required = true, schema = @Schema(type = "string")) @PathVariable String workerId, + @Parameter(description = "Number of threads to set for the worker.", required = true, schema = @Schema(type = "string")) @PathVariable String numThreads) { log.info("*** REST CALL *** updateWorkerNumJobExecThreads (workerId='"+workerId+"', numThreads='"+numThreads+"')..."); @@ -1823,10 +1674,10 @@ public GsonUTCDateAdapter() { * This cookie, can then be used to make future requests. * */ - @ApiOperation(value = "Authenticates the user via GET.", tags = {"Security"}, produces = "application/json") + @Operation(summary = "Authenticates the user via GET.", tags = {"Security"}) @RequestMapping(value="/authenticate", method = GET) public @ResponseBody String authenticateViaGet( - @ApiIgnore final HttpSession session) { + @Parameter(hidden = true) final HttpSession session) { log.debug("/authenticate call got through CWS security!"); return "{\"status\" : \"SUCCESS\", \"session\" : \"" + session.getId() + "\"}"; } @@ -1840,10 +1691,10 @@ public GsonUTCDateAdapter() { * This cookie, can then be used to make future requests. * */ - @ApiOperation(value = "Authenticates the user via POST.", tags = {"Security"}, produces = "application/json") + @Operation(summary = "Authenticates the user via POST.", tags = {"Security"}) @RequestMapping(value = "/authenticate", method = POST) public @ResponseBody String authenticateViaPost( - @ApiIgnore final HttpSession session, + @Parameter(hidden = true) final HttpSession session, HttpServletResponse response) { log.debug("/authenticate call got through CWS security!"); return "{\"status\" : \"SUCCESS\", \"session\" : \"" + session.getId() + "\"}"; @@ -1854,15 +1705,12 @@ public GsonUTCDateAdapter() { * Validates CWS token (checks for expiration) * */ - @ApiOperation(value = "Validates CWS token.", tags = {"Security"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "cwsToken", value = "CWS token to validate.", required = true, paramType = "query") - }) + @Operation(summary = "Validates CWS token.", tags = {"Security"}) @RequestMapping(value = "/validateCwsToken", method = POST) public @ResponseBody String validateCwsToken( - @ApiIgnore final HttpSession session, + @Parameter(hidden = true) final HttpSession session, HttpServletResponse response, - @RequestParam String cwsToken) { + @Parameter(description = "CWS token to validate.", required = true, schema = @Schema(type = "string")) @RequestParam String cwsToken) { log.trace("validateCwsToken... (cwsToken="+cwsToken+", session.id="+session.getId()+")"); boolean isValid = session.getId().equals(cwsToken); log.trace("/validateCwsToken returning " + isValid); @@ -1877,12 +1725,9 @@ public GsonUTCDateAdapter() { * For testing purposes - if you want to send messages to the built-in ActiveMQ broker * */ - @ApiOperation(value = "Posts a message to an AMQ queue.", tags = {"Messaging"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "payload", value = "Payload to post to the queue.", required = true, paramType = "query") - }) + @Operation(summary = "Posts a message to an AMQ queue.", tags = {"Messaging"}) @RequestMapping(value = "/postAmqTopic", method = GET) - public @ResponseBody String postAmqTopic(@RequestParam(value = "payload", required=true) final String payload) { + public @ResponseBody String postAmqTopic(@Parameter(description = "Payload to post to the queue.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "payload", required=true) final String payload) { log.debug("posting AMQ topic... payload: " + payload); jmsProcessInitiatorTemplate.send(new MessageCreator() { public Message createMessage(Session session) throws JMSException { @@ -1900,15 +1745,11 @@ public Message createMessage(Session session) throws JMSException { * can make a call to get data from an external resource. * */ - @ApiOperation(value = "Makes an external HTTP GET request.", tags = {"External"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "url", value = "URL to make the GET request to.", required = true, paramType = "query"), - @ApiImplicitParam(name = "acceptType", value = "Accept type for the request.", required = false, paramType = "query") - }) + @Operation(summary = "Makes an external HTTP GET request.", tags = {"External"}) @RequestMapping(value = "/externalGetReq", method = GET) public @ResponseBody String externalGetReq( - @RequestParam(value = "url", required=true) final String url, - @RequestParam(value = "acceptType", required=false) final String acceptType) { + @Parameter(description = "URL to make the GET request to.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "url", required=true) final String url, + @Parameter(description = "Accept type for the request.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "acceptType", required=false) final String acceptType) { log.debug("making external HTTP GET (acceptType=" + acceptType + ") request with URL: " + url); RestCallResult restCallResult = null; @@ -1932,16 +1773,12 @@ public Message createMessage(Session session) throws JMSException { * This call expects a parameter with a key of 'data' that holds the POST data body. * */ - @ApiOperation(value = "Makes an external HTTP POST request.", tags = {"External"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "url", value = "URL to make the POST request to.", required = true, paramType = "query"), - @ApiImplicitParam(name = "contentType", value = "Content type for the request.", required = false, paramType = "query") - }) + @Operation(summary = "Makes an external HTTP POST request.", tags = {"External"}) @RequestMapping(value = "/externalPostReq", method = POST) public @ResponseBody String externalPostReq( HttpServletRequest request, - @RequestParam(value = "url", required=true) final String url, - @RequestParam(value = "contentType", required=false) final String contentType) { + @Parameter(description = "URL to make the POST request to.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "url", required=true) final String url, + @Parameter(description = "Content type for the request.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "contentType", required=false) final String contentType) { String postPayload = request.getParameter("data"); log.debug("making external HTTP POST request with URL=" + url + ", contentType="+contentType+", and postPayload=" + postPayload); @@ -1968,18 +1805,13 @@ public Message createMessage(Session session) throws JMSException { * This call expects a parameter with a key of 'data' that holds the PUT data body. * */ - @ApiOperation(value = "Makes an external HTTP PUT request.", tags = {"External"}, produces = "application/json") - @ApiImplicitParams({ - @ApiImplicitParam(name = "url", value = "URL to make the PUT request to.", required = true, paramType = "query"), - @ApiImplicitParam(name = "contentType", value = "Content type for the request.", required = false, paramType = "query"), - @ApiImplicitParam(name = "payload", value = "Payload to post to the queue.", required = true, paramType = "body") - }) + @Operation(summary = "Makes an external HTTP PUT request.", tags = {"External"}) @RequestMapping(value = "/externalPutReq", method = PUT) public @ResponseBody String externalPutReq( HttpServletRequest request, - @RequestParam(value = "url", required=true) final String url, - @RequestParam(value = "contentType", required=false) final String contentType, - @RequestBody String payload) { + @Parameter(description = "URL to make the PUT request to.", required = true, schema = @Schema(type = "string")) @RequestParam(value = "url", required=true) final String url, + @Parameter(description = "Content type for the request.", required = false, schema = @Schema(type = "string")) @RequestParam(value = "contentType", required=false) final String contentType, + @Parameter(description = "Payload to post to the queue.", required = true, schema = @Schema(type = "string")) @RequestBody String payload) { log.debug("making external HTTP PUT request with URL=" + url + ", contentType="+contentType+", and payload=" + payload); diff --git a/cws-service/src/main/java/jpl/cws/controller/SwaggerConfig.java b/cws-service/src/main/java/jpl/cws/controller/SwaggerConfig.java index bde2d486..6173b100 100644 --- a/cws-service/src/main/java/jpl/cws/controller/SwaggerConfig.java +++ b/cws-service/src/main/java/jpl/cws/controller/SwaggerConfig.java @@ -1,39 +1,32 @@ package jpl.cws.controller; import org.springframework.context.annotation.Bean; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; +import org.springframework.context.annotation.Configuration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.Components; -@EnableSwagger2 -@EnableWebMvc +@Configuration public class SwaggerConfig { + @Bean - public Docket api() { - return new Docket(DocumentationType.SWAGGER_2) - .select() - .apis(RequestHandlerSelectors.any()) - .paths(PathSelectors.any()) - .build() - .apiInfo(apiInfo()) - .pathMapping("/rest/"); - } - - // add apikey to swagger auth - - - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("CWS API") - .description("Documentation of the endpoints used by CWS. Once authenticated, requests can be made to these endpoints.\nTo authenticate, right click on this page --> Inspect --> Click the 'Application' tab --> Select the URL under the Cookies tab on the left --> Copy the value of the cwsToken cookie.") - .version("2.7.0") // update this each CWS release - .license("Apache 2.0") - .licenseUrl("https://github.com/NASA-AMMOS/common-workflow-service?tab=Apache-2.0-1-ov-file") - .build(); + public OpenAPI api() { + return new OpenAPI() + .info(new Info() + .title("CWS API") + .description("Documentation of the endpoints used by CWS. Once authenticated, requests can be made to these endpoints.\nTo authenticate, right click on this page --> Inspect --> Click the 'Application' tab --> Select the URL under the Cookies tab on the left --> Copy the value of the cwsToken cookie.") + .version("2.7.0") // update this each CWS release + .license(new License() + .name("Apache 2.0") + .url("https://github.com/NASA-AMMOS/common-workflow-service?tab=Apache-2.0-1-ov-file"))) + .addSecurityItem(new SecurityRequirement().addList("cwsToken")) + .components(new Components() + .addSecuritySchemes("cwsToken", new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("cwsToken"))); } } diff --git a/cws-service/src/main/java/jpl/cws/process/initiation/RepeatingDelayInitiator.java b/cws-service/src/main/java/jpl/cws/process/initiation/RepeatingDelayInitiator.java index c0740888..93462d3a 100644 --- a/cws-service/src/main/java/jpl/cws/process/initiation/RepeatingDelayInitiator.java +++ b/cws-service/src/main/java/jpl/cws/process/initiation/RepeatingDelayInitiator.java @@ -17,7 +17,7 @@ public class RepeatingDelayInitiator extends CwsProcessInitiator { private static final Logger log = LoggerFactory.getLogger(RepeatingDelayInitiator.class); private Long delayBetweenProcesses; - private static final Long DEFAULT_DELAY_BETWEEN_PROCESSES = new Long(10000); + private static final Long DEFAULT_DELAY_BETWEEN_PROCESSES = 10000L; private Long xtorDelayBetweenProcesses; // used for re-construction private Long maxRepeats; diff --git a/cws-service/src/main/java/jpl/cws/process/initiation/aws/S3Initiator.java b/cws-service/src/main/java/jpl/cws/process/initiation/aws/S3Initiator.java index ff701fe7..294811a4 100644 --- a/cws-service/src/main/java/jpl/cws/process/initiation/aws/S3Initiator.java +++ b/cws-service/src/main/java/jpl/cws/process/initiation/aws/S3Initiator.java @@ -3,7 +3,7 @@ import jpl.cws.core.code.CodeService; import jpl.cws.partner.finding.custom.S3PartnerFinder; import jpl.cws.process.initiation.CwsProcessInitiator; -import org.python.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -52,10 +52,15 @@ public class S3Initiator extends CwsProcessInitiator implements InitializingBean @Autowired private ApplicationContext springContext; @Autowired public CodeService cwsCodeService; - final ThreadFactory schedThreadFactory = new ThreadFactoryBuilder() - .setNameFormat("sched-%d") - .setDaemon(true) - .build(); + final ThreadFactory schedThreadFactory = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "sched-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } + }; private ExecutorService schedPool; // diff --git a/cws-service/src/main/java/jpl/cws/process/initiation/filearrival/nio/FileInitiator.java b/cws-service/src/main/java/jpl/cws/process/initiation/filearrival/nio/FileInitiator.java index 16f03bf9..e36a3983 100644 --- a/cws-service/src/main/java/jpl/cws/process/initiation/filearrival/nio/FileInitiator.java +++ b/cws-service/src/main/java/jpl/cws/process/initiation/filearrival/nio/FileInitiator.java @@ -20,9 +20,9 @@ import java.util.Map; import java.util.Set; -import javax.jms.Message; -import javax.jms.MessageListener; -import javax.jms.TextMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; +import jakarta.jms.TextMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/cws-service/src/main/java/jpl/cws/process/initiation/message/MessageArrivalInitiator.java b/cws-service/src/main/java/jpl/cws/process/initiation/message/MessageArrivalInitiator.java index b2341977..a41741cd 100644 --- a/cws-service/src/main/java/jpl/cws/process/initiation/message/MessageArrivalInitiator.java +++ b/cws-service/src/main/java/jpl/cws/process/initiation/message/MessageArrivalInitiator.java @@ -3,11 +3,11 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import javax.jms.BytesMessage; -import javax.jms.Message; -import javax.jms.MessageListener; +import jakarta.jms.BytesMessage; +import jakarta.jms.Message; +import jakarta.jms.MessageListener; -import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.artemis.jms.client.ActiveMQTopic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; diff --git a/cws-service/src/main/java/jpl/cws/scheduler/Scheduler.java b/cws-service/src/main/java/jpl/cws/scheduler/Scheduler.java index f43e8745..8f259822 100644 --- a/cws-service/src/main/java/jpl/cws/scheduler/Scheduler.java +++ b/cws-service/src/main/java/jpl/cws/scheduler/Scheduler.java @@ -1,26 +1,16 @@ package jpl.cws.scheduler; -import static jpl.cws.core.db.SchedulerDbService.FAILED_TO_SCHEDULE; -import static jpl.cws.core.db.SchedulerDbService.PENDING; - -import java.io.*; -import java.net.URLDecoder; -import java.sql.Timestamp; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; - -import javax.annotation.Resource; -import javax.jms.BytesMessage; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; - +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; +import jpl.cws.core.db.SchedulerDbService; +import jpl.cws.core.db.SchedulerJob; import org.camunda.bpm.engine.RepositoryService; import org.camunda.bpm.engine.repository.ProcessDefinition; -import org.joda.time.DateTime; +import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -30,9 +20,19 @@ import org.springframework.jms.core.MessageCreator; import org.springframework.util.MultiValueMap; -import de.ruedigermoeller.serialization.FSTObjectOutput; -import jpl.cws.core.db.SchedulerDbService; -import jpl.cws.core.db.SchedulerJob; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import static jpl.cws.core.db.SchedulerDbService.FAILED_TO_SCHEDULE; +import static jpl.cws.core.db.SchedulerDbService.PENDING; public class Scheduler implements InitializingBean { private static final Logger log = LoggerFactory.getLogger(Scheduler.class); @@ -156,7 +156,7 @@ public SchedulerJob scheduleProcess(String procDefKey, log.trace("process business key not specified. Created one automatically: " + uuid); } - Timestamp tsNow = new Timestamp(DateTime.now().getMillis()); + Timestamp tsNow = Timestamp.from(Instant.now()); SchedulerJob schedulerJob = new SchedulerJob( uuid, tsNow, // createdTime @@ -223,16 +223,9 @@ private boolean isExistingProcDef(String procDefKey) { * Constructs a byte array representing the request process data payload * */ - private byte[] createProcReqData(Map msgPayload) - throws IOException { - try ( - ByteArrayOutputStream os = new ByteArrayOutputStream(); - FSTObjectOutput out = new FSTObjectOutput(os); - ) - { - out.writeObject(msgPayload); - return os.toByteArray(); - } + private byte[] createProcReqData(Map msgPayload) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + String json = objectMapper.writeValueAsString(msgPayload); + return json.getBytes(StandardCharsets.UTF_8); } - } diff --git a/cws-service/src/main/java/jpl/cws/scheduler/SchedulerQueueUtils.java b/cws-service/src/main/java/jpl/cws/scheduler/SchedulerQueueUtils.java index 8ad110d9..ae44e248 100644 --- a/cws-service/src/main/java/jpl/cws/scheduler/SchedulerQueueUtils.java +++ b/cws-service/src/main/java/jpl/cws/scheduler/SchedulerQueueUtils.java @@ -12,10 +12,11 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; -import org.apache.activemq.broker.BrokerRegistry; -import org.apache.activemq.broker.BrokerService; -import org.apache.activemq.broker.jmx.BrokerViewMBean; -import org.apache.activemq.broker.jmx.QueueViewMBean; +import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; +import org.apache.activemq.artemis.api.core.management.QueueControl; +import org.apache.activemq.artemis.api.core.management.ResourceNames; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -26,7 +27,7 @@ public class SchedulerQueueUtils { // TODO: make these values come from configuration - @Value("${cws.broker.obj.name}") private String BROKER_OBJ_NAME; + @Value("${cws.broker.obj.name:org.apache.activemq.artemis:broker=cwsConsoleBroker}") private String BROKER_OBJ_NAME; @Value("${cws.amq.jmx.service.url}") private String AMQ_JMX_SERVICE_URL; private static JMXServiceURL url; @@ -41,12 +42,15 @@ public class SchedulerQueueUtils { public void logSchedulerQueues() { try { System.out.println("------------------------ SCHEDULER QUEUES -------------------------------"); - for (ObjectName queueName : getBrokerViewMBean().getQueues()) { - QueueViewMBean queueMbean = getQueueViewMBean(queueName); - System.out.println(" "+queueMbean.getName() + - " : [enqueues: " + queueMbean.getEnqueueCount() + - ", dequeues: " + queueMbean.getDequeueCount() + - ", inFlights: " + queueMbean.getInFlightCount()+"]"); + ActiveMQServerControl serverControl = getActiveMQServerControl(); + String[] queueNames = serverControl.getQueueNames(); + + for (String queueName : queueNames) { + QueueControl queueControl = getQueueControl(queueName); + System.out.println(" "+queueControl.getName() + + " : [enqueues: " + queueControl.getMessageCount() + + ", dequeues: " + (queueControl.getMessageCount() - queueControl.getDeliveringCount()) + + ", inFlights: " + queueControl.getDeliveringCount()+"]"); } System.out.println("------------------------------------------------------------------------"); } catch (Exception e) { @@ -58,20 +62,22 @@ public void logSchedulerQueues() { /** * */ - public Set getAmqClients() throws Exception { - Set uniqueClients = new HashSet(); - Map map = BrokerRegistry.getInstance().getBrokers(); - BrokerService brokerService = map.get("cwsConsoleBroker"); - org.apache.activemq.broker.Connection[] clients = brokerService.getBroker().getClients(); - for (org.apache.activemq.broker.Connection client : clients) { - log.trace("CLIENT: "+client); - log.trace(" stats: "+client.getStatistics()); - log.trace(" connId: "+client.getConnectionId()); - log.trace(" remoteAddr: "+client.getRemoteAddress()); - log.trace(" connector: "+client.getConnector()); - uniqueClients.add(client); + public Set getAmqClients() throws Exception { + Set uniqueServers = new HashSet<>(); + + // For Artemis, we'll use JMX to get server information instead + // The embedded server instance is not easily accessible from this context + try { + ActiveMQServerControl serverControl = getActiveMQServerControl(); + log.trace("ARTEMIS SERVER CONTROL: " + serverControl); + log.trace(" version: " + serverControl.getVersion()); + log.trace(" nodeID: " + serverControl.getNodeID()); + // Note: We can't get the actual server instance, but we have control access + } catch (Exception e) { + log.debug("Could not get ActiveMQ server control: " + e.getMessage()); } - return uniqueClients; + + return uniqueServers; } @@ -87,9 +93,11 @@ public boolean queueExists(String queueName) throws Exception { } log.debug("CHECKING FOR EXISTENCE OF SCHEDULER QUEUE: '"+queueName+"' ..."); - for (ObjectName existingQueueName : getBrokerViewMBean().getQueues()) { - QueueViewMBean queueMbean = getQueueViewMBean(existingQueueName); - if (queueMbean.getName().equals(queueName)) { + ActiveMQServerControl serverControl = getActiveMQServerControl(); + String[] queueNames = serverControl.getQueueNames(); + + for (String existingQueueName : queueNames) { + if (existingQueueName.equals(queueName)) { log.debug("SCHEDULER QUEUE: '"+queueName+"' EXISTS!"); return true; } @@ -108,10 +116,9 @@ public void addQueue(String queueName) throws Exception { throw new IllegalAccessException("queueName was null or empty!"); } log.debug("CREATING SCHEDULER QUEUE '"+queueName+"' ..."); - String operationName="addQueue"; - Object[] params = {queueName}; - String[] sig = {"java.lang.String"}; - getConn().invoke(getActiveMQ(), operationName, params, sig); + + ActiveMQServerControl serverControl = getActiveMQServerControl(); + serverControl.createQueue(queueName, queueName, true, "ANYCAST"); } @@ -152,17 +159,18 @@ private ObjectName getActiveMQ() throws MalformedObjectNameException { /** * */ - private BrokerViewMBean getBrokerViewMBean() throws Exception { - return (BrokerViewMBean) MBeanServerInvocationHandler.newProxyInstance( - getConn(), getActiveMQ(), BrokerViewMBean.class, true); + private ActiveMQServerControl getActiveMQServerControl() throws Exception { + return (ActiveMQServerControl) MBeanServerInvocationHandler.newProxyInstance( + getConn(), getActiveMQ(), ActiveMQServerControl.class, true); } /** * */ - private QueueViewMBean getQueueViewMBean(ObjectName queueName) throws Exception { - return (QueueViewMBean) MBeanServerInvocationHandler.newProxyInstance( - getConn(), queueName, QueueViewMBean.class, true); + private QueueControl getQueueControl(String queueName) throws Exception { + ObjectName queueObjectName = new ObjectName(ResourceNames.QUEUE + queueName); + return (QueueControl) MBeanServerInvocationHandler.newProxyInstance( + getConn(), queueObjectName, QueueControl.class, true); } } diff --git a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java index c937c5c1..596d1611 100644 --- a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java +++ b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java @@ -1,10 +1,14 @@ package jpl.cws.service; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonNull; import com.google.gson.JsonObject; -import de.ruedigermoeller.serialization.FSTObjectInput; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; import jpl.cws.core.code.CodeService; import jpl.cws.core.db.SchedulerDbService; import jpl.cws.core.log.CwsEmailerService; @@ -42,11 +46,8 @@ import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; -import javax.jms.BytesMessage; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; import java.io.*; +import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -1109,10 +1110,10 @@ public JsonArray getPendingProcessesJSON() throws Exception { try { // Get process variables as a map // - byte[] procVarsAsBytes = (byte[]) row.get("proc_variables"); - FSTObjectInput in = new FSTObjectInput(new ByteArrayInputStream(procVarsAsBytes)); - Map procVars = (Map) in.readObject(); - in.close(); + byte[] procVarsAsBytes = (byte[])row.get("proc_variables"); + String json = new String(procVarsAsBytes, StandardCharsets.UTF_8).trim(); + ObjectMapper objectMapper = new ObjectMapper(); + Map procVars = objectMapper.readValue(json, Map.class); if (procVars == null) { procVars = new HashMap(); diff --git a/cws-service/src/main/resources/cws.properties b/cws-service/src/main/resources/cws.properties index 27c984fa..d4ec800c 100644 --- a/cws-service/src/main/resources/cws.properties +++ b/cws-service/src/main/resources/cws.properties @@ -5,3 +5,11 @@ # THIS FILE WILL GET OVERWRITTEN AT CONFIGURE TIME, # BEFORE CWS STARTS UP FOR THE FIRST TIME # + +# SpringDoc OpenAPI Configuration +springdoc.api-docs.path=/v3/api-docs +springdoc.swagger-ui.path=/swagger-ui +springdoc.swagger-ui.enabled=true +springdoc.api-docs.enabled=true +springdoc.swagger-ui.config-url=/v3/api-docs/swagger-config +springdoc.swagger-ui.url=/v3/api-docs diff --git a/cws-tasks/pom.xml b/cws-tasks/pom.xml index 218b20c8..b2a80d62 100644 --- a/cws-tasks/pom.xml +++ b/cws-tasks/pom.xml @@ -29,13 +29,36 @@ - + + org.slf4j + slf4j-api + provided + org.apache.logging.log4j log4j-slf4j-impl provided + + org.camunda.bpm + camunda-engine-plugin-spin + + + org.camunda.bpm + camunda-engine + + + org.camunda.bpm.model + camunda-bpmn-model + provided + + + org.camunda.commons + camunda-commons-typed-values + provided + + @@ -49,48 +72,30 @@ com.google.code.gson gson - - - commons-lang - commons-lang + org.apache.commons + commons-lang3 - commons-io commons-io - org.apache.commons commons-exec - org.apache.commons - commons-email + commons-email2-jakarta - - commons-configuration - commons-configuration + org.apache.commons + commons-configuration2 - - javax.mail - mail + jakarta.mail + jakarta.mail-api - - - org.glassfish.jersey.core - jersey-client - - - - org.glassfish.grizzly - grizzly-http-server - - junit junit @@ -99,22 +104,20 @@ - ${project.artifactId} maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy-dependencies diff --git a/cws-tasks/src/main/java/jpl/cws/task/CmdLineExecTask.java b/cws-tasks/src/main/java/jpl/cws/task/CmdLineExecTask.java index a429f645..ba16bdbd 100644 --- a/cws-tasks/src/main/java/jpl/cws/task/CmdLineExecTask.java +++ b/cws-tasks/src/main/java/jpl/cws/task/CmdLineExecTask.java @@ -23,15 +23,15 @@ import org.apache.commons.exec.LogOutputStream; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.environment.EnvironmentUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.Expression; import com.google.gson.Gson; import edu.rice.cs.util.ArgumentTokenizer; -import jersey.repackaged.com.google.common.base.Function; -import jersey.repackaged.com.google.common.collect.Collections2; +import java.util.function.Function; +import java.util.stream.Collectors; import org.camunda.spin.plugin.variable.SpinValues; import org.camunda.spin.plugin.variable.value.JsonValue; @@ -186,7 +186,7 @@ public void executeTask() { // success = false; // false until proven success for (String successCode : successExitValuesSplit) { - success = new Boolean(Integer.parseInt(successCode.trim()) == exitValue); + success = Integer.parseInt(successCode.trim()) == exitValue; if (success) { break; // found a match, so must be success } @@ -200,7 +200,7 @@ public void executeTask() { // Detect whether a certain event case applies (based on exit code) // for (String eventCode : exitCodeEventsMap.keySet()) { - if (new Boolean(Integer.parseInt(eventCode) == exitValue)) { + if (Integer.parseInt(eventCode) == exitValue) { cmdOutputFields.event = exitCodeEventsMap.get(eventCode); break; // can only be one event } @@ -220,16 +220,16 @@ public void executeTask() { // Get trimmed output variable String outputStr = StringUtils.join( - Collections2.transform(sortedLines.values(), new StripOrderId()), '\n'); + sortedLines.values().stream().map(new StripOrderId()).collect(Collectors.toList()), '\n'); // Set stdout output into variable // - String stdoutStr = StringUtils.join(Collections2.transform(stdOutLines, new StripOrderId()), '\n'); + String stdoutStr = StringUtils.join(stdOutLines.stream().map(new StripOrderId()).collect(Collectors.toList()), '\n'); cmdOutputFields.stdout = stdoutStr; // Set stderr output into variable // - String stderrStr = StringUtils.join(Collections2.transform(stdErrLines, new StripOrderId()), '\n'); + String stderrStr = StringUtils.join(stdErrLines.stream().map(new StripOrderId()).collect(Collectors.toList()), '\n'); cmdOutputFields.stderr = stderrStr; setStdOutVariables(stdOutLines); @@ -327,8 +327,8 @@ public List getLines() { private class StripOrderId implements Function { @SuppressWarnings("unchecked") @Override - public Object apply(Object f) { - return f.toString().replaceFirst("\\d+__", ""); + public T apply(F f) { + return (T) f.toString().replaceFirst("\\d+__", ""); } } diff --git a/cws-tasks/src/main/java/jpl/cws/task/CwsTask.java b/cws-tasks/src/main/java/jpl/cws/task/CwsTask.java index a1f343b5..3d779151 100644 --- a/cws-tasks/src/main/java/jpl/cws/task/CwsTask.java +++ b/cws-tasks/src/main/java/jpl/cws/task/CwsTask.java @@ -120,10 +120,18 @@ public void run() { // complete in Camunda before we can look at Camunda's records // to get the true status of the process instance. sleep(1000); - ProcessService cwsProcessService = (ProcessService) SpringApplicationContext.getBean("cwsProcessService"); - cwsProcessService.sendProcEventTopicMessageWithRetries(null, null, null, null, "sync"); + + // Check if Spring context is available before accessing beans + if (SpringApplicationContext.isContextAvailable()) { + ProcessService cwsProcessService = (ProcessService)SpringApplicationContext.getBean("cwsProcessService"); + cwsProcessService.sendProcEventTopicMessageWithRetries(null, null, null, null, "sync"); + } else { + log.warn("Spring ApplicationContext not available during worker notification. Skipping sync message."); + } } catch (InterruptedException e) { e.printStackTrace(); + } catch (Exception e) { + log.error("Error during worker notification: " + e.getMessage(), e); } } }).start(); diff --git a/cws-tasks/src/main/java/jpl/cws/task/EmailTask.java b/cws-tasks/src/main/java/jpl/cws/task/EmailTask.java index 9326fac1..28791889 100644 --- a/cws-tasks/src/main/java/jpl/cws/task/EmailTask.java +++ b/cws-tasks/src/main/java/jpl/cws/task/EmailTask.java @@ -1,8 +1,8 @@ package jpl.cws.task; -import org.apache.commons.mail.Email; -import org.apache.commons.mail.HtmlEmail; -import org.apache.commons.mail.SimpleEmail; +import org.apache.commons.mail2.jakarta.Email; +import org.apache.commons.mail2.jakarta.HtmlEmail; +import org.apache.commons.mail2.jakarta.SimpleEmail; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.Expression; diff --git a/cws-tasks/src/main/java/jpl/cws/task/ScheduleProcTask.java b/cws-tasks/src/main/java/jpl/cws/task/ScheduleProcTask.java index 41d33de3..9c5712a8 100644 --- a/cws-tasks/src/main/java/jpl/cws/task/ScheduleProcTask.java +++ b/cws-tasks/src/main/java/jpl/cws/task/ScheduleProcTask.java @@ -37,8 +37,12 @@ public class ScheduleProcTask extends CwsTask { public ScheduleProcTask() { log.debug("ScheduleProcTask constructor..."); - processService = (ProcessService) SpringApplicationContext.getBean("cwsProcessService"); - log.debug("ScheduleProcTask() processService = " + processService); + if (SpringApplicationContext.isContextAvailable()) { + processService = (ProcessService)SpringApplicationContext.getBean("cwsProcessService"); + log.debug("ScheduleProcTask() processService = " + processService); + } else { + log.warn("Spring ApplicationContext not available during ScheduleProcTask construction. Bean will be retrieved later if needed."); + } } @Override @@ -53,6 +57,16 @@ public void initParams() throws Exception { @Override public void executeTask() throws Exception { this.setOutputVariable("procDefKey", procDefKey); + + // Get processService if not already available + if (processService == null) { + if (SpringApplicationContext.isContextAvailable()) { + processService = (ProcessService)SpringApplicationContext.getBean("cwsProcessService"); + } else { + throw new IllegalStateException("Cannot execute ScheduleProcTask: Spring ApplicationContext is not available and ProcessService is null."); + } + } + processService.sendProcScheduleMessageWithRetries( procDefKey, procVariables, procBusinessKey, initiationKey, priority); diff --git a/cws-tasks/src/main/java/jpl/cws/task/SetVariablesTask.java b/cws-tasks/src/main/java/jpl/cws/task/SetVariablesTask.java index f208fc9d..9fb886ce 100644 --- a/cws-tasks/src/main/java/jpl/cws/task/SetVariablesTask.java +++ b/cws-tasks/src/main/java/jpl/cws/task/SetVariablesTask.java @@ -2,7 +2,11 @@ import java.util.Iterator; -import org.apache.commons.configuration.HierarchicalINIConfiguration; +import org.apache.commons.configuration2.INIConfiguration; +import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; +import org.apache.commons.configuration2.builder.fluent.Parameters; +import org.apache.commons.configuration2.ex.ConfigurationException; +import java.io.File; import org.camunda.bpm.engine.delegate.Expression; /** @@ -30,24 +34,30 @@ public void initParams() throws Exception { public void executeTask() throws Exception { log.info("SetVariablesTask (" + srcPropertiesFileString + ")"); - HierarchicalINIConfiguration fc = new HierarchicalINIConfiguration(); - - fc.setDelimiterParsingDisabled(true); // Do not convert any values into an .ini List - fc.load(srcPropertiesFileString); - - for (String sectionName : fc.getSections()) { - Iterator keysIter = fc.getSection(sectionName).getKeys(); + try { + FileBasedConfigurationBuilder builder = + new FileBasedConfigurationBuilder(INIConfiguration.class) + .configure(new Parameters().fileBased().setFile(new File(srcPropertiesFileString))); - while (keysIter.hasNext()) { - String key = keysIter.next(); - String keyToSet = (sectionName==null ? "" : sectionName+"_")+key; - - keyToSet = keyToSet.replace("..", "."); // Fixes bug in library that causes variables with one dot to be converted to dotdot + INIConfiguration fc = builder.getConfiguration(); + + for (String sectionName : fc.getSections()) { + Iterator keysIter = fc.getSection(sectionName).getKeys(); - String valToSet = fc.getProperty((sectionName==null?"":sectionName)+"."+key).toString(); - log.info("SETTING VARIABLE: "+keyToSet+" = "+valToSet); - this.setOutputVariableActualName(keyToSet, valToSet); + while (keysIter.hasNext()) { + String key = keysIter.next(); + String keyToSet = (sectionName==null ? "" : sectionName+"_")+key; + + keyToSet = keyToSet.replace("..", "."); // Fixes bug in library that causes variables with one dot to be converted to dotdot + + String valToSet = fc.getProperty((sectionName==null?"":sectionName)+"."+key).toString(); + log.info("SETTING VARIABLE: "+keyToSet+" = "+valToSet); + this.setOutputVariableActualName(keyToSet, valToSet); + } } + } catch (ConfigurationException e) { + log.error("Error loading configuration file: " + srcPropertiesFileString, e); + throw new Exception("Failed to load configuration file: " + srcPropertiesFileString, e); } log.info("SetVariablesTask operation complete."); diff --git a/cws-test/pom.xml b/cws-test/pom.xml index 14230df3..30144a7a 100644 --- a/cws-test/pom.xml +++ b/cws-test/pom.xml @@ -28,6 +28,7 @@ org.springframework + test @@ -43,6 +44,7 @@ gov.nasa.jpl.ammos.ids.cws cws-service + test @@ -60,14 +62,39 @@ cws-engine-service + + com.icegreen + greenmail + test + + - dumbster - dumbster + org.eclipse.jetty + jetty-server + test - - org.glassfish.grizzly - grizzly-http-server + org.eclipse.jetty.ee10 + jetty-ee10-servlet + test + + + + jakarta.ws.rs + jakarta.ws.rs-api + test + + + + org.glassfish.jersey.core + jersey-client + test + + + + org.glassfish.jersey.inject + jersey-hk2 + test @@ -79,8 +106,13 @@ selenium-chrome-driver - org.seleniumhq.selenium - selenium-http-jdk-client + jakarta.mail + jakarta.mail-api + test + + + org.eclipse.angus + jakarta.mail @@ -115,15 +147,77 @@ test - + + org.seleniumhq.selenium + selenium-api + test + + + org.seleniumhq.selenium + selenium-remote-driver + test + + + org.seleniumhq.selenium + selenium-support + test + + + org.seleniumhq.selenium + selenium-chromium-driver + test + + + + org.slf4j + slf4j-api + test + + + com.google.code.gson + gson + test + + + commons-io + commons-io + test + + + + com.sun.mail + jakarta.mail + + + + org.camunda.spin + camunda-spin-core + test + + + org.camunda.bpm + camunda-engine + provided + + + org.python + jython-standalone + test + + + jakarta.servlet + jakarta.servlet-api + provided + + maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + true @@ -254,7 +348,6 @@ org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin.version} ${surefireArgLine} -Xms256m -Xmx2048m @@ -272,7 +365,6 @@ org.apache.maven.plugins maven-failsafe-plugin - ${maven-failsafe-plugin.version} ${testArgLine} @@ -297,12 +389,10 @@ org.apache.maven.plugins maven-install-plugin - ${maven-install-plugin.version} org.codehaus.mojo exec-maven-plugin - ${exec-maven-plugin.version} post-integration-test @@ -318,7 +408,6 @@ maven-resources-plugin - ${maven-resources-plugin.version} copy-resources @@ -377,7 +466,6 @@ org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy-dependencies diff --git a/cws-test/src/test/java/jpl/cws/test/CmdLineExecTaskTest.java b/cws-test/src/test/java/jpl/cws/test/CmdLineExecTaskTest.java index 18e2c67d..c9af58b5 100644 --- a/cws-test/src/test/java/jpl/cws/test/CmdLineExecTaskTest.java +++ b/cws-test/src/test/java/jpl/cws/test/CmdLineExecTaskTest.java @@ -28,7 +28,6 @@ public class CmdLineExecTaskTest extends CwsTestBase { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - // MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/CustomMethodCallTest.java b/cws-test/src/test/java/jpl/cws/test/CustomMethodCallTest.java index f155507e..80a15e09 100644 --- a/cws-test/src/test/java/jpl/cws/test/CustomMethodCallTest.java +++ b/cws-test/src/test/java/jpl/cws/test/CustomMethodCallTest.java @@ -26,7 +26,6 @@ public class CustomMethodCallTest extends CwsTestBase { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - //MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/Cws331Test.java b/cws-test/src/test/java/jpl/cws/test/Cws331Test.java index 87187bed..6afefc7a 100644 --- a/cws-test/src/test/java/jpl/cws/test/Cws331Test.java +++ b/cws-test/src/test/java/jpl/cws/test/Cws331Test.java @@ -29,7 +29,6 @@ public class Cws331Test extends CwsTestBase { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - // MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/EmailTaskTest.java b/cws-test/src/test/java/jpl/cws/test/EmailTaskTest.java index 1606ac68..adbeaf40 100644 --- a/cws-test/src/test/java/jpl/cws/test/EmailTaskTest.java +++ b/cws-test/src/test/java/jpl/cws/test/EmailTaskTest.java @@ -14,8 +14,13 @@ import org.camunda.bpm.engine.test.mock.Mocks; import org.junit.*; -import com.dumbster.smtp.SimpleSmtpServer; -import com.dumbster.smtp.SmtpMessage; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; +import jakarta.mail.Message; +import jakarta.mail.Multipart; +import jakarta.mail.Part; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; /** * Tests related to EmailTask @@ -26,20 +31,21 @@ public class EmailTaskTest { @Rule public ProcessEngineRule processEngineRule = new ProcessEngineRule(); - private SimpleSmtpServer smtpServer; + private GreenMail greenMail; private static final int SMTP_PORT = 2525; - @Before // deciding where to ultimately put the jUnit integration - public void setUp() { - //MockitoAnnotations.initMocks(this); - smtpServer = SimpleSmtpServer.start(SMTP_PORT); // need to start on non-privileged port due to permission issues - } + @Before // deciding where to ultimately put the jUnit integration + public void setUp() { + ServerSetup smtpSetup = new ServerSetup(SMTP_PORT, null, ServerSetup.PROTOCOL_SMTP); + greenMail = new GreenMail(smtpSetup); + greenMail.start(); + } @After - public void tearDown() { - Mocks.reset(); - smtpServer.stop(); - } + public void tearDown() { + Mocks.reset(); + greenMail.stop(); + } /** @@ -63,21 +69,68 @@ public void testCase1() { fail("Unexpected exception: "+e); } - // Leverage Dumbster mock SMTP server to check email arrival - // - assertTrue("Expected to receive exactly one email, but got: "+smtpServer.getReceivedEmailSize(), smtpServer.getReceivedEmailSize() == 1); - Iterator emailIter = smtpServer.getReceivedEmail(); - SmtpMessage email = emailIter.next(); - assertTrue(email.getHeaderValue("Subject").equals("test from CWS")); - - System.out.println("BODY: " + email.getBody()); - - // verify newlines got translated correctly - assertTrue(email.getBody().contains("thishaslinebreaks!")); - - // verify to/from - assertTrue(email.getHeaderValue("From").equals("user@domain.com")); - assertTrue(email.getHeaderValue("To").equals("user@domain.org")); + // Verify email arrival using GreenMail + assertTrue("Expected to receive exactly one email, but got: "+greenMail.getReceivedMessages().length, greenMail.getReceivedMessages().length == 1); + MimeMessage email = greenMail.getReceivedMessages()[0]; + try { + assertTrue(email.getSubject().equals("test from CWS")); + String body = extractBody(email); + System.out.println("BODY: " + body); + // verify newlines got translated correctly + assertTrue(body.contains("thishaslinebreaks!")); + // verify to/from + assertTrue(email.getFrom()[0].toString().contains("user@domain.com")); + assertTrue(email.getAllRecipients()[0].toString().contains("user@domain.org")); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected exception reading email: "+e); + } + + } + + private String extractBody(MimeMessage message) throws Exception { + Object content = message.getContent(); + if (content instanceof String) { + return (String) content; + } + if (content instanceof MimeMultipart) { + return getTextFromMultipart((MimeMultipart) content); + } + if (content instanceof Multipart) { + return getTextFromMultipart((Multipart) content); + } + return String.valueOf(content); + } + + private String getTextFromMultipart(Multipart multipart) throws Exception { + String html = null; + String plain = null; + for (int i = 0; i < multipart.getCount(); i++) { + Part part = multipart.getBodyPart(i); + Object partContent = part.getContent(); + String contentType = part.getContentType(); + if (partContent instanceof String) { + String text = (String) partContent; + if (contentType != null && contentType.toLowerCase().contains("text/html")) { + html = text; + } else if (contentType != null && contentType.toLowerCase().contains("text/plain")) { + plain = text; + } + } else if (partContent instanceof Multipart) { + String nested = getTextFromMultipart((Multipart) partContent); + if (nested != null && !nested.isEmpty()) { + // Prefer nested html if available + if (part.getContentType().toLowerCase().contains("text/html")) { + html = nested; + } else if (plain == null) { + plain = nested; + } + } + } + } + if (html != null) return html; + if (plain != null) return plain; + return ""; } } \ No newline at end of file diff --git a/cws-test/src/test/java/jpl/cws/test/FileInitiatorTest.java b/cws-test/src/test/java/jpl/cws/test/FileInitiatorTest.java index c9f16e4b..e21968de 100644 --- a/cws-test/src/test/java/jpl/cws/test/FileInitiatorTest.java +++ b/cws-test/src/test/java/jpl/cws/test/FileInitiatorTest.java @@ -33,7 +33,6 @@ public class FileInitiatorTest { @Before public void setUp() { - //MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/Issue16Test.java b/cws-test/src/test/java/jpl/cws/test/Issue16Test.java index e430817b..7104b01e 100644 --- a/cws-test/src/test/java/jpl/cws/test/Issue16Test.java +++ b/cws-test/src/test/java/jpl/cws/test/Issue16Test.java @@ -27,7 +27,6 @@ public class Issue16Test extends CwsTestBase { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - // MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest.java b/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest.java index 1e1577d1..d64e6ec8 100644 --- a/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest.java +++ b/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest.java @@ -6,15 +6,17 @@ import java.util.Map; -import javax.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientBuilder; import org.camunda.bpm.engine.runtime.ProcessInstance; import org.camunda.bpm.engine.test.Deployment; import org.camunda.bpm.engine.test.ProcessEngineRule; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +28,7 @@ public class RestGetTaskTest extends CwsTestBase { private static final Logger log = LoggerFactory.getLogger(RestGetTaskTest.class); - private HttpServer server; + private Server server; @Rule public ProcessEngineRule processEngineRule = new ProcessEngineRule(); @@ -36,22 +38,26 @@ public void setUp() throws Exception { // Setup a HTTP server that will receive REST calls // during the lifetime of these tests. // - server = HttpServer.createSimpleServer(null, 9999); - server.getServerConfiguration().addHttpHandler(new HttpHandler() { - public void service(Request request, Response response) throws Exception { + server = new Server(9999); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + context.addServlet(new ServletHolder(new HttpServlet() { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException { response.setContentType("text/plain"); String resp = "bar"; response.setContentLength(resp.length()); response.getWriter().write(resp); } - }, "/foo"); + }), "/foo"); server.start(); } @After public void tearDown() throws Exception { - server.shutdownNow(); + server.stop(); } /** diff --git a/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest2.java b/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest2.java index 6b863759..8d6e7989 100644 --- a/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest2.java +++ b/cws-test/src/test/java/jpl/cws/test/RestGetTaskTest2.java @@ -6,10 +6,12 @@ import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.impl.el.FixedValue; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -18,23 +20,26 @@ public class RestGetTaskTest2 { private static final String VAR_PREFIX = TestDelegateExecution.VAR_PREFIX; - - private HttpServer server; + private Server server; @Before public void setUp() throws Exception { // Setup a HTTP server that will receive REST calls // during the lifetime of these tests. // - server = HttpServer.createSimpleServer(null, 9999); - server.getServerConfiguration().addHttpHandler(new HttpHandler() { - public void service(Request request, Response response) throws Exception { + server = new Server(9999); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + context.addServlet(new ServletHolder(new HttpServlet() { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException { response.setContentType("text/plain"); String resp = "bar"; response.setContentLength(resp.length()); response.getWriter().write(resp); } - }, "/foo"); + }), "/foo"); server.start(); } diff --git a/cws-test/src/test/java/jpl/cws/test/RestPostTaskTest.java b/cws-test/src/test/java/jpl/cws/test/RestPostTaskTest.java index f40f32f0..7b9426d8 100644 --- a/cws-test/src/test/java/jpl/cws/test/RestPostTaskTest.java +++ b/cws-test/src/test/java/jpl/cws/test/RestPostTaskTest.java @@ -8,10 +8,12 @@ import org.camunda.bpm.engine.runtime.ProcessInstance; import org.camunda.bpm.engine.test.Deployment; import org.camunda.bpm.engine.test.ProcessEngineRule; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,7 +25,7 @@ public class RestPostTaskTest extends CwsTestBase { private static final Logger log = LoggerFactory.getLogger(RestPostTaskTest.class); - private HttpServer server; + private Server server; @Rule public ProcessEngineRule processEngineRule = new ProcessEngineRule(); @@ -33,27 +35,38 @@ public void setUp() throws Exception { // Setup a HTTP server that will receive REST calls // during the lifetime of these tests. // - server = HttpServer.createSimpleServer(null, 9999); - server.getServerConfiguration().addHttpHandler(new HttpHandler() { - public void service(Request request, Response response) throws Exception { - // Only accept POST requests - if (!request.getMethod().getMethodString().equals("POST")) { - response.setStatus(500); - return; - } + server = new Server(9999); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + context.addServlet(new ServletHolder(new HttpServlet() { + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException { response.setContentType("text/plain"); String resp = "bar"; response.setContentLength(resp.length()); response.getWriter().write(resp); } - }, "/foo"); + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException { + response.setStatus(500); + } + }), "/foo"); + + // Return 404 for any other path + context.addServlet(new ServletHolder(new HttpServlet() { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws java.io.IOException { + resp.setStatus(404); + } + }), "/*"); server.start(); } @After public void tearDown() throws Exception { - server.shutdownNow(); + server.stop(); } /** diff --git a/cws-test/src/test/java/jpl/cws/test/SleepTaskTest.java b/cws-test/src/test/java/jpl/cws/test/SleepTaskTest.java index e6e859e8..7d83d56f 100644 --- a/cws-test/src/test/java/jpl/cws/test/SleepTaskTest.java +++ b/cws-test/src/test/java/jpl/cws/test/SleepTaskTest.java @@ -23,7 +23,6 @@ public class SleepTaskTest { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - //MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/SubProcessBoundaryCatchTest.java b/cws-test/src/test/java/jpl/cws/test/SubProcessBoundaryCatchTest.java index 1ab022f8..822318e5 100644 --- a/cws-test/src/test/java/jpl/cws/test/SubProcessBoundaryCatchTest.java +++ b/cws-test/src/test/java/jpl/cws/test/SubProcessBoundaryCatchTest.java @@ -27,7 +27,6 @@ public class SubProcessBoundaryCatchTest extends CwsTestBase { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - //MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/java/jpl/cws/test/TaskInstanceCountTest.java b/cws-test/src/test/java/jpl/cws/test/TaskInstanceCountTest.java index 8d00a121..7f34caaa 100644 --- a/cws-test/src/test/java/jpl/cws/test/TaskInstanceCountTest.java +++ b/cws-test/src/test/java/jpl/cws/test/TaskInstanceCountTest.java @@ -28,7 +28,6 @@ public class TaskInstanceCountTest { @Before // deciding where to ultimately put the jUnit integration public void setUp() { - //MockitoAnnotations.initMocks(this); } @After diff --git a/cws-test/src/test/resources/camunda.cfg.xml b/cws-test/src/test/resources/camunda.cfg.xml index 02bd089a..70987a4a 100644 --- a/cws-test/src/test/resources/camunda.cfg.xml +++ b/cws-test/src/test/resources/camunda.cfg.xml @@ -7,7 +7,6 @@ - diff --git a/cws-test/src/test/resources/configure_with_jacoco.sh b/cws-test/src/test/resources/configure_with_jacoco.sh index e62af2a5..269881df 100755 --- a/cws-test/src/test/resources/configure_with_jacoco.sh +++ b/cws-test/src/test/resources/configure_with_jacoco.sh @@ -264,7 +264,7 @@ rm -f ${ROOT}/config/my.cnf sleep 1 if [ "$RECONFIGURE" = true ]; then - ${JAVA_HOME}/bin/java -classpath "./installer/*" -javaagent:./server/apache-tomcat-${TOMCAT_VER}/lib/org.jacoco.agent-0.8.7-runtime.jar=destfile=./installer-jacoco.exec,append=false jpl.cws.task.CwsInstaller --reconfigure + ${JAVA_HOME}/bin/java -classpath "./installer/*" -javaagent:./server/apache-tomcat-${TOMCAT_VER}/lib/org.jacoco.agent-0.8.13-runtime.jar=destfile=./installer-jacoco.exec,append=false jpl.cws.task.CwsInstaller --reconfigure else - ${JAVA_HOME}/bin/java -classpath "./installer/*" -javaagent:./server/apache-tomcat-${TOMCAT_VER}/lib/org.jacoco.agent-0.8.7-runtime.jar=destfile=./installer-jacoco.exec,append=false jpl.cws.task.CwsInstaller + ${JAVA_HOME}/bin/java -classpath "./installer/*" -javaagent:./server/apache-tomcat-${TOMCAT_VER}/lib/org.jacoco.agent-0.8.13-runtime.jar=destfile=./installer-jacoco.exec,append=false jpl.cws.task.CwsInstaller fi diff --git a/cws-test/src/test/resources/log4j.properties b/cws-test/src/test/resources/log4j.properties index 4cfabad8..c6902e83 100644 --- a/cws-test/src/test/resources/log4j.properties +++ b/cws-test/src/test/resources/log4j.properties @@ -8,6 +8,6 @@ log4j.appender.CA.layout.ConversionPattern=cws-tasks-TEST: %d{ISO8601}{UTC} %-5p log4j.logger.org.camunda=INFO log4j.logger.org.springframework=INFO log4j.logger.jpl.cws=DEBUG -log4j.logger.javax.activation.level=INFO +log4j.logger.jakarta.activation.level=INFO log4j.logger.org.apache.activemq.transport=INFO diff --git a/cws-ui/pom.xml b/cws-ui/pom.xml index e5582d56..7eb7541d 100644 --- a/cws-ui/pom.xml +++ b/cws-ui/pom.xml @@ -33,6 +33,24 @@ org.camunda.spin camunda-spin-dataformat-all + + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + @@ -44,9 +62,11 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${java.version} + + -parameters + diff --git a/cws-ui/src/main/webapp/WEB-INF/springmvc-servlet.xml b/cws-ui/src/main/webapp/WEB-INF/springmvc-servlet.xml index bcf7c1cd..478cf790 100644 --- a/cws-ui/src/main/webapp/WEB-INF/springmvc-servlet.xml +++ b/cws-ui/src/main/webapp/WEB-INF/springmvc-servlet.xml @@ -33,12 +33,8 @@ - - - - UTF-8 - - + + - - - - + + - + __PROC_START_REQ_LISTENER_XML__ - + @@ -128,7 +127,7 @@ __PROC_START_REQ_LISTENER_XML__ - + @@ -149,7 +148,7 @@ __PROC_START_REQ_LISTENER_XML__ - + @@ -164,7 +163,7 @@ __PROC_START_REQ_LISTENER_XML__ - + @@ -178,7 +177,7 @@ __PROC_START_REQ_LISTENER_XML__ - + @@ -192,7 +191,7 @@ __PROC_START_REQ_LISTENER_XML__ - + @@ -214,7 +213,7 @@ __PROC_START_REQ_LISTENER_XML__ - + diff --git a/install/cws-engine/process_start_req_listener.xml b/install/cws-engine/process_start_req_listener.xml index 5b99646c..c44ce1cf 100644 --- a/install/cws-engine/process_start_req_listener.xml +++ b/install/cws-engine/process_start_req_listener.xml @@ -1,5 +1,5 @@ - + diff --git a/install/cws-ui/api-docs.ftl b/install/cws-ui/api-docs.ftl index 4bb0dd01..d6a23015 100644 --- a/install/cws-ui/api-docs.ftl +++ b/install/cws-ui/api-docs.ftl @@ -14,46 +14,57 @@ diff --git a/install/cws-ui/applicationContext.xml b/install/cws-ui/applicationContext.xml index 9260dda0..1e4244ee 100644 --- a/install/cws-ui/applicationContext.xml +++ b/install/cws-ui/applicationContext.xml @@ -15,7 +15,7 @@ http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd http://www.springframework.org/schema/jee - http://www.springframework.org/schema/jee/spring-jee-3.1.xsd"> + http://www.springframework.org/schema/jee/spring-jee.xsd"> @@ -55,6 +55,7 @@ + @@ -93,71 +94,25 @@ - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + - + - + @@ -171,7 +126,7 @@ - + @@ -183,7 +138,7 @@ - + @@ -195,7 +150,7 @@ - + @@ -208,7 +163,7 @@ - + @@ -220,7 +175,7 @@ - + @@ -241,7 +196,7 @@ - + @@ -264,7 +219,7 @@ - + @@ -276,7 +231,7 @@ - + @@ -289,7 +244,7 @@ - + diff --git a/install/cws-ui/broker.xml b/install/cws-ui/broker.xml new file mode 100644 index 00000000..c6b5c1ad --- /dev/null +++ b/install/cws-ui/broker.xml @@ -0,0 +1,176 @@ + + + + + + cwsConsoleBroker + + true + + NIO + + __CWS_ROOT_DIR__/server/artemis/data/paging + + __CWS_ROOT_DIR__/server/artemis/data/bindings + + __CWS_ROOT_DIR__/server/artemis/data/journal + + __CWS_ROOT_DIR__/server/artemis/data/large-messages + + 2 + + 10 + + 4096 + + 10M + + + 3333333 + + + false + + + true + org.apache.activemq.artemis + true + + + org.apache.activemq.artemis.integration.logging.Log4jLogDelegateFactory + + + tcp://__CWS_AMQ_HOST__:__CWS_AMQ_PORT__ + + + + tcp://__CWS_AMQ_HOST__:__CWS_AMQ_PORT__?connectionTtl=60000;clientFailureCheckPeriod=5000 + + + false + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/install/cws-ui/cws-ui.properties b/install/cws-ui/cws-ui.properties index d5bed0df..54be273f 100755 --- a/install/cws-ui/cws-ui.properties +++ b/install/cws-ui/cws-ui.properties @@ -74,3 +74,11 @@ cws.camunda.version=__CWS_CAMUNDA_VERSION__ cws.java.version=__CWS_JAVA_VERSION__ cws.java.home=__CWS_JAVA_HOME__ +# SpringDoc OpenAPI Configuration +springdoc.api-docs.path=/v3/api-docs +springdoc.swagger-ui.path=/swagger-ui +springdoc.swagger-ui.enabled=true +springdoc.api-docs.enabled=true +springdoc.swagger-ui.config-url=/v3/api-docs/swagger-config +springdoc.swagger-ui.url=/v3/api-docs + diff --git a/install/cws-ui/snippets.ftl b/install/cws-ui/snippets.ftl index 7257e5d6..8e6495df 100644 --- a/install/cws-ui/snippets.ftl +++ b/install/cws-ui/snippets.ftl @@ -73,7 +73,7 @@ NOTE: For each external code library (JAR) (referenced by Java import statements) put the JAR in the following place: - CWS Console Server: cws/server/apache-tomcat-9.0.75/lib + CWS Console Server: cws/server/apache-tomcat-10.1.36/lib diff --git a/install/cws_camunda-bpm-tomcat-7.20.0-lib.zip b/install/cws_camunda-bpm-tomcat-7.20.0-lib.zip deleted file mode 100644 index 2088822b..00000000 Binary files a/install/cws_camunda-bpm-tomcat-7.20.0-lib.zip and /dev/null differ diff --git a/install/cws_camunda-bpm-tomcat-7.20.0.zip b/install/cws_camunda-bpm-tomcat-7.20.0.zip deleted file mode 100644 index 41f19d7a..00000000 Binary files a/install/cws_camunda-bpm-tomcat-7.20.0.zip and /dev/null differ diff --git a/install/dev/bpmn/test_model_outputs.bpmn b/install/dev/bpmn/test_model_outputs.bpmn index 90682302..66ec893f 100644 --- a/install/dev/bpmn/test_model_outputs.bpmn +++ b/install/dev/bpmn/test_model_outputs.bpmn @@ -44,7 +44,7 @@ var typedFileValue2 = org.camunda.bpm.engine.variable.Variables.fileValue("shutt execution.setVariable("output_shuttle_gif", typedFileValue2); var typedFileValue3 = org.camunda.bpm.engine.variable.Variables.fileValue("jpl.png").file(new java.io.File("../../../install/dev/bpmn/images/jpl.png")).mimeType("image/png").encoding("UTF-8").create(); execution.setVariable("output_jpl_png", typedFileValue3); -execution.setVariable("output_image_url", "https://www.jpl.nasa.gov/twitter-card.jpg"); +execution.setVariable("output_image_url", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/NASA_Mars_Rover.jpg/500px-NASA_Mars_Rover.jpg"); diff --git a/install/docker/console-db-es-ls-kibana/docker-compose.yml b/install/docker/console-db-es-ls-kibana/docker-compose.yml index 7dff56f9..ed22d5aa 100644 --- a/install/docker/console-db-es-ls-kibana/docker-compose.yml +++ b/install/docker/console-db-es-ls-kibana/docker-compose.yml @@ -124,11 +124,12 @@ services: timeout: 2s retries: 12 volumes: - # - ../../.keystore:/home/cws_user/cws/server/apache-tomcat-9.0.75/conf/.keystore:ro - # - ../../tomcat_lib/cws_truststore.jks:/home/cws_user/cws/server/apache-tomcat-9.0.75/lib/cws_truststore.jks:ro + # - ../../.keystore:/home/cws_user/cws/server/apache-tomcat-10.1.36/conf/.keystore:ro + # - ../../tomcat_lib/cws_truststore.jks:/home/cws_user/cws/server/apache-tomcat-10.1.36/lib/cws_truststore.jks:ro # - ~/.cws/creds:/root/.cws/creds:ro - ./config.properties:/home/cws_user/config.properties:ro - - console-logs-volume:/home/cws_user/cws/server/apache-tomcat-9.0.75/logs + - ~/.camunda/license.txt:/root/.camunda/license.txt:ro + - console-logs-volume:/home/cws_user/cws/server/apache-tomcat-10.1.36/logs networks: - cws-network cws-worker: @@ -151,11 +152,12 @@ services: - ES_HOST=es - ES_PORT=9200 volumes: - # - ../../.keystore:/home/cws_user/cws/server/apache-tomcat-9.0.75/conf/.keystore:ro - # - ../../tomcat_lib/cws_truststore.jks:/home/cws_user/cws/server/apache-tomcat-9.0.75/lib/cws_truststore.jks:ro + # - ../../.keystore:/home/cws_user/cws/server/apache-tomcat-10.1.36/conf/.keystore:ro + # - ../../tomcat_lib/cws_truststore.jks:/home/cws_user/cws/server/apache-tomcat-10.1.36/lib/cws_truststore.jks:ro # - ~/.cws/creds:/root/.cws/creds:ro - ./worker-config.properties:/home/cws_user/config.properties:ro - - worker1-logs-volume:/home/cws_user/cws/server/apache-tomcat-9.0.75/logs + - ~/.camunda/license.txt:/root/.camunda/license.txt:ro + - worker1-logs-volume:/home/cws_user/cws/server/apache-tomcat-10.1.36/logs networks: - cws-network ldapsearch: diff --git a/install/docker/cws-image/Dockerfile b/install/docker/cws-image/Dockerfile index a8b7be12..8b1743eb 100644 --- a/install/docker/cws-image/Dockerfile +++ b/install/docker/cws-image/Dockerfile @@ -1,11 +1,12 @@ -FROM oraclelinux:8 +FROM oraclelinux:9 RUN yum update -y && \ yum install -y mysql java-17-openjdk java-17-openjdk-devel rsync which && \ - yum clean all + yum clean all && \ + rm -rf /var/cache/yum -ENV JAVA_HOME /usr/lib/jvm/java-openjdk -ENV HOME /root +ENV JAVA_HOME=/usr/lib/jvm/java-openjdk +ENV HOME=/root ENV TZ=America/Los_Angeles RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone @@ -20,6 +21,5 @@ RUN mkdir -p /root/.cws && echo "changeit" > /root/.cws/creds # For time check ADD getTime.java . -ADD joda-time-2.1.jar . ENTRYPOINT [ "./wait_for_db_es_console.sh" ] diff --git a/install/docker/cws-image/build.sh b/install/docker/cws-image/build.sh index 9f1f520f..4bad4389 100755 --- a/install/docker/cws-image/build.sh +++ b/install/docker/cws-image/build.sh @@ -18,14 +18,12 @@ if [ ! -f "$CWS_PACKAGE" ]; then fi cp "$CWS_PACKAGE" . -cp ../../../cws-core/cws-core-libs/joda-time-2.1.jar . echo "Building CWS docker image. Version = $ver" docker build -t nasa-ammos/common-workflow-service:$ver . rm cws_server.tar.gz -rm joda-time-2.1.jar echo echo "Done building!" diff --git a/install/docker/cws-image/getTime.java b/install/docker/cws-image/getTime.java index 2c35ebaf..1d58cb2f 100644 --- a/install/docker/cws-image/getTime.java +++ b/install/docker/cws-image/getTime.java @@ -1,5 +1,5 @@ import java.util.Calendar; -import org.joda.time.DateTime; +import java.time.Instant; import java.util.TimeZone; import java.sql.Timestamp; @@ -7,9 +7,10 @@ public class getTime { public static void main(String[] args) { - Timestamp thisMachineTime = new Timestamp(DateTime.now().getMillis()); + Timestamp thisMachineTime = Timestamp.from(Instant.now()); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); TimeZone tz = Calendar.getInstance().getTimeZone(); + System.out.println("CWS Docker Container"); System.out.println("Current Time: " + sdf.format(thisMachineTime)); // Format the date using the specified pattern. System.out.println("Time Zone: " + tz.getDisplayName()); } diff --git a/install/docker/cws-image/startup.sh b/install/docker/cws-image/startup.sh index c610d239..66317ced 100755 --- a/install/docker/cws-image/startup.sh +++ b/install/docker/cws-image/startup.sh @@ -1,12 +1,12 @@ #!/bin/bash -javac -cp joda-time-2.1.jar getTime.java -java -cp .:joda-time-2.1.jar getTime +javac getTime.java +java getTime -ls /home/cws_user/cws/server/apache-tomcat-9.0.75/logs +ls /home/cws_user/cws/server/apache-tomcat-10.1.36/logs # Clear out any previous logs before starting (Note: Previous logs will cause CWS not to start) -rm -rf /home/cws_user/cws/server/apache-tomcat-9.0.75/logs/* +rm -rf /home/cws_user/cws/server/apache-tomcat-10.1.36/logs/* cd cws ./configure.sh ../config.properties Y diff --git a/install/docker/worker-ls/docker-compose.yml b/install/docker/worker-ls/docker-compose.yml index 4833c77d..94ad62b0 100644 --- a/install/docker/worker-ls/docker-compose.yml +++ b/install/docker/worker-ls/docker-compose.yml @@ -41,7 +41,7 @@ services: - ES_PORT=9200 volumes: - ./config.properties:/home/cws_user/config.properties:ro - - worker2-logs-volume:/home/cws_user/cws/server/apache-tomcat-9.0.75/logs + - worker2-logs-volume:/home/cws_user/cws/server/apache-tomcat-10.1.36/logs volumes: worker2-logs-volume: diff --git a/install/engine-rest/web.xml b/install/engine-rest/web.xml index 2c9887db..bbb780f3 100644 --- a/install/engine-rest/web.xml +++ b/install/engine-rest/web.xml @@ -1,6 +1,6 @@ - + xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"> + + Internal CWS dependencies - no external vulnerabilities + ^pkg:maven/gov\.nasa\.jpl\.ammos\.ids\.cws/.*$ + .* + + + diff --git a/pom.xml b/pom.xml index 39461a32..8f775349 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ common-workflow-service 2.7.0 pom - cws + common-workflow-service CWS @@ -20,19 +20,23 @@ - 5.15.16 - 3.1.4 - 2.16.93 - - 7.20.0 - 1.9.0 - 1.21 - 1.10 - 1.3 + 2.42.0 + 3.27.6 + 5.3.8 + 21.3.12 + 2.8.13 + 2.2.37 + 2.34.3 + 7.23.0-ee + 1.28.0 + 2.12.0 + 1.5.0 1.5 - 2.7 - 2.6 - 1.5 + 2.17.0 + 3.19.0 + 1.14.0 + 2.0.0-M1 + 2.0.2 ${project.version} ${cws.version} ${cws.version} @@ -40,49 +44,59 @@ ${cws.version} ${cws.version} ${cws.version} - 1.6 - 2.3.31 - 1.55 - 2.3.11 + 2.1.5 + 2.3.33 + 12.1.1 2.8.9 - 2.2.220 - - 0.8.7 + 3.1.11 + 33.5.0-jre + 2.3.232 + 0.8.13 17 - 1.4.7 - 2.6 - 1.1 - 2.1 + 2.0.5 + 2.1.5 + 2.1.0 + 2.1.1 + 3.1.0 + 3.1.0 + 4.13.1 - 2.7.2b3 - 2.7.2 - - 3.8.0 - 3.0.1 - 3.1.0 - 2.5.2 - 1.6.0 - 2.22.0 + 2.7.4 + 3.5.6 + + 3.14.1 + 2.8 + 3.6.1 + 2.6 + 3.1.4 + 3.3.2 + 3.5.0 + 3.1.4 + 3.21.0 + 3.4.2 + 1.4.0 + 2.16 2.22.2 - 4.3.1 - 3.5.6 - 8.0.28 + 3.5.15 + 9.4.0 4.0.3 - 1.0.4 UTF-8 - 2.3.2 - 4.13.0 - 3.1.0 - - 2.17.1 - 2.17.1 + 2.5.0 + 4.35.0 + 6.0.0 + 2.25.2 + 1.7.36 + 1.3.5 false - 5.3.31 + 6.2.4 + 6.2.4 + 2.15.2 2.22.2 - 9.0.75 - 4.0.3 - 4.24 - 2.6.2 + 10.1.36 + 5.1.3 + 2.10.4 + 12.1.6 + 2.19.1 @@ -107,23 +121,45 @@ - - maven2 - https://repo1.maven.org/maven2 - - - repository.jboss.org-public - JBoss.org Maven repository - https://repository.jboss.org/nexus/content/groups/public - - - redhat - Redhat Maven Repository Public - https://maven.repository.redhat.com/ga/ + + camunda-bpm-nexus-ee + camunda-bpm-nexus + https://camunda.jfrog.io/artifactory/private + + + central + Maven Central + https://repo.maven.apache.org/maven2 + + org.apache.tomcat + tomcat-annotations-api + ${tomcat-catalina.version} + + + org.apache.activemq + artemis-core-client + ${activemq.version} + + + org.apache.activemq + artemis-server + ${activemq.version} + + + + io.swagger.core.v3 + swagger-models-jakarta + ${swagger.core.version} + + + io.swagger.core.v3 + swagger-annotations-jakarta + ${swagger.core.version} + gov.nasa.jpl.ammos.ids.cws cws-core @@ -134,7 +170,6 @@ gov.nasa.jpl.ammos.ids.cws cws-tasks ${cws.tasks.version} - gov.nasa.jpl.ammos.ids.cws @@ -175,14 +210,6 @@ import pom - - - org.camunda.spin - camunda-spin-dataformat-all - import - pom - ${camunda-spin.version} - org.camunda.bpm camunda-engine-plugin-spin @@ -200,13 +227,13 @@ org.graalvm.js js - 23.0.2 + ${graalvm.js.version} runtime org.graalvm.js js-scriptengine - 23.0.2 + ${graalvm.js.version} runtime @@ -215,10 +242,6 @@ camunda-engine ${camunda.version} - - - - org.slf4j slf4j-api @@ -233,43 +256,35 @@ ${jython-standalone.version} - org.camunda.bpm camunda-engine-spring ${camunda.version} - - - - - - org.camunda.bpm.identity camunda-identity-ldap ${camunda.version} - - - - - - - provided - - - - javax.servlet - javax.servlet-api - ${servlet-api.version} + + + commons-logging + commons-logging + + provided + + jakarta.servlet + jakarta.servlet-api + ${servlet-api.version} + provided + - javax.annotation - javax.annotation-api - 1.3.2 + jakarta.annotation + jakarta.annotation-api + ${jakarta.annotation.version} @@ -296,14 +311,9 @@ ${gson.version} - javax.jms - jms - ${jms.version} - - - javax.jms - javax.jms-api - 2.0.1 + com.google.guava + guava + ${guava.version} @@ -313,8 +323,8 @@ ${mariadb-java-client.version} - mysql - mysql-connector-java + com.mysql + mysql-connector-j ${mysql-connector.version} @@ -333,9 +343,14 @@ - commons-lang - commons-lang - ${commons-lang.version} + org.apache.commons + commons-text + ${commons-text.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} commons-io @@ -349,7 +364,7 @@ org.apache.commons - commons-email + commons-email2-jakarta ${commons-email.version} @@ -357,90 +372,89 @@ commons-fileupload ${commons-fileupload.version} + + org.springframework + spring-web + ${spring.framework.version} + org.apache.commons commons-compress ${commons-compress.version} - - org.apache.xbean - xbean-spring - ${xbean-spring.version} - - - javax.mail - mail - ${javax.mail.version} + jakarta.mail + jakarta.mail-api + ${jakarta.mail-api.version} - - - org.apache.activemq - activemq-broker - ${activemq.version} + org.eclipse.angus + jakarta.mail + ${jakarta.mail.version} - org.apache.activemq - activemq-client - ${activemq.version} - - - org.apache.activemq - activeio-core - ${activeio-core.version} + jakarta.activation + jakarta.activation-api + ${jakarta.activation.version} + org.apache.activemq - activemq-kahadb-store + artemis-jakarta-client ${activemq.version} org.apache.activemq - activemq-spring + artemis-jakarta-server ${activemq.version} + + + commons-logging + commons-logging + + + commons-beanutils + commons-beanutils + + + + jakarta.jms + jakarta.jms-api + ${jakarta.jms.version} + + - org.apache.geronimo.specs - geronimo-jms_1.1_spec - 1.1.1 - - - org.apache.geronimo.specs - geronimo-jta_1.0.1B_spec - 1.0.1 - - - org.apache.geronimo.specs - geronimo-j2ee-management_1.1_spec - 1.0.1 - - - org.apache.geronimo.specs - geronimo-jacc_1.1_spec - 1.0.1 + org.apache.commons + commons-configuration2 + ${commons-configuration.version} + + + commons-logging + commons-logging + + + + - org.apache.geronimo.specs - geronimo-j2ee-connector_1.5_spec - 2.0.0 + org.apache.logging.log4j + log4j-core + ${log4j.version} + - commons-configuration - commons-configuration - ${commons-configuration.version} + org.slf4j + slf4j-api + ${slf4j.version} - - - org.apache.logging.log4j log4j-slf4j-impl - ${slf4j-log4j2-bind.version} - provided + ${log4j.version} @@ -449,6 +463,13 @@ ${log4j.version} + + commons-logging + commons-logging + ${commons-logging.version} + provided + + org.springframework @@ -496,29 +517,17 @@ freemarker ${freemarker.version} + + org.freemarker + freemarker-jakarta + ${freemarker.version} + org.mybatis mybatis ${mybatis.version} - - joda-time - joda-time - ${joda-time.version} - - - - org.glassfish.jersey.core - jersey-client - ${jersey-client.version} - - - - de.ruedigermoeller - fst - ${fst.version} - @@ -532,17 +541,11 @@ camunda-bpm-assert ${camunda.version} test - - - - - - - + org.assertj assertj-core - 3.22.0 + ${assertj.version} test @@ -575,12 +578,25 @@ ${selenium.version} test - org.seleniumhq.selenium - selenium-http-jdk-client + selenium-api + ${selenium.version} + + + org.seleniumhq.selenium + selenium-remote-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-support + ${selenium.version} + + + org.seleniumhq.selenium + selenium-chromium-driver ${selenium.version} - test org.jacoco @@ -592,22 +608,22 @@ jacoco-maven-plugin ${jacoco.version} - - org.mockito - mockito-core - ${mockito.version} - test - - - dumbster - dumbster - ${dumbster.version} + + com.icegreen + greenmail + ${greenmail.version} + test + + + org.eclipse.jetty + jetty-server + ${jetty.version} test - - org.glassfish.grizzly - grizzly-http-server - ${grizzly-http-server.version} + + org.eclipse.jetty.ee10 + jetty-ee10-servlet + ${jetty.version} test @@ -618,13 +634,6 @@ test - - io.github.bonigarcia - webdrivermanager - 5.5.0 - test - - org.tuckey urlrewritefilter @@ -641,19 +650,66 @@ org.webjars bootstrap - 3.4.0 + ${bootstrap.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + - io.springfox - springfox-swagger2 - 3.0.0 + com.fasterxml.jackson.core + jackson-core + ${jackson.version} - io.springfox - springfox-swagger-ui - 3.0.0 + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs.version} + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey.version} + + + org.camunda.spin + camunda-spin-dataformat-all + ${camunda.version} + + + com.sun.mail + jakarta.mail + ${jakarta.mail.sun.version} - @@ -671,9 +727,91 @@ - @project.groupId@ + org.jacoco jacoco-maven-plugin - @project.version@ + ${jacoco.version} + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + org.codehaus.mojo + exec-maven-plugin + ${exec-maven-plugin.version} + + + org.owasp + dependency-check-maven + ${owasp.dependency.check.version} + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + true + false + true + true + file://${maven.multiModuleProjectDirectory}/version-rules.xml + @@ -695,10 +833,87 @@ org.apache.maven.plugins maven-war-plugin - 3.3.1 + ${maven-war-plugin.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-maven + + enforce + + + + + [3.9.6,) + + + [17,) + + + Best Practice is to always define plugin versions! + true + true + true + clean,deploy,site + + + Duplicate dependency versions found in pom.xml + + + No repositories should be defined in your pom.xml, please remove them and define them in a settings.xml or an external file. + + camunda-bpm-nexus-ee + central + + + + + + + + + org.owasp + dependency-check-maven + ${owasp.dependency.check.version} + + + owasp-suppressions.xml + + + + + + ${env.NVD_API_KEY} + + 168 + + ${user.home}/.dependency-check-data + HTML + target/dependency-check-reports + false + false + false + false + true + true + true + true + true + true + false + true + true + true + true + true + true + false + - - diff --git a/utils.sh b/utils.sh index 6ebdc2f8..f66d7bc5 100755 --- a/utils.sh +++ b/utils.sh @@ -6,8 +6,8 @@ # Update versions as necessary export CWS_VER='2.7.0' # update this each CWS release -export CAMUNDA_VER='7.20.0' -export TOMCAT_VER='9.0.75' +export CAMUNDA_VER='7.23.0-ee' +export TOMCAT_VER='10.1.36' # update this each Camunda update export LOGSTASH_VER='8.12.0' # Prints the provided string, tagging with the script that called it diff --git a/version-rules.xml b/version-rules.xml new file mode 100644 index 00000000..7a8b2e88 --- /dev/null +++ b/version-rules.xml @@ -0,0 +1,15 @@ + + + + .*-alpha.* + .*-beta.* + .*-M\d+ + .*-milestone.* + .*-RC\d+ + .*-rc\d+ + .*-SNAPSHOT + +
cws/server/apache-tomcat-9.0.75/lib
cws/server/apache-tomcat-10.1.36/lib