diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..7d99ec9c4e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Force gradlew (shell script) to always use LF +gradlew text eol=lf + +# Force gradlew.bat (Windows batch) to always use CRLF +gradlew.bat text eol=crlf \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec879d84c3..89a6d92fff 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,14 +4,48 @@ on: push: branches: - main + - release + tags: + - 'v*' + - '[0-9]+.[0-9]+.[0-9]+*' pull_request: branches: - main + release: + types: [published] + workflow_dispatch: + inputs: + skip_tests: + description: 'Skip integration tests' + required: false + default: false + type: boolean + create_prerelease: + description: 'Create a prerelease from this build' + required: false + default: false + type: boolean jobs: - build: + build-gradle: + name: Build with Gradle runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: donkeytest + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: - uses: actions/checkout@v4 @@ -21,22 +55,249 @@ jobs: java-version: '17' java-package: 'jdk+fx' distribution: 'zulu' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-version: '8.5' + + - name: Install local dependencies + run: ./libs-local/install-local-deps.sh - - name: Build OIE (signed) - if: github.ref == 'refs/heads/main' - working-directory: server - run: ant -f mirth-build.xml + - name: Configure signing + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + if [ -n "$KEYSTORE_BASE64" ]; then + echo "Using provided signing certificate from secrets..." + echo "$KEYSTORE_BASE64" | base64 -d > server/keystore.jks + cat > server/keystore.properties << EOF + key.keystore=\${basedir}/keystore.jks + key.storepass=${KEYSTORE_PASSWORD} + key.alias=${KEY_ALIAS} + key.keypass=${KEY_PASSWORD} + EOF + else + echo "No signing secrets configured - generating self-signed certificate..." + rm -f server/keystore.jks + keytool -genkeypair \ + -alias oie \ + -keyalg RSA \ + -keysize 2048 \ + -validity 365 \ + -storetype PKCS12 \ + -keystore server/keystore.jks \ + -storepass changeit \ + -keypass changeit \ + -dname "CN=Open Integration Engine, OU=Development, O=OIE, L=Unknown, ST=Unknown, C=US" \ + -noprompt + cat > server/keystore.properties << EOF + key.keystore=\${basedir}/keystore.jks + key.storepass=changeit + key.alias=oie + key.keypass=changeit + EOF + echo "::notice::Using self-signed certificate for JAR signing" + fi - - name: Build OIE (unsigned) - if: github.ref != 'refs/heads/main' - working-directory: server - run: ant -f mirth-build.xml -DdisableSigning=true + - name: Build with Gradle + run: ./gradlew build assembleSetup buildLinuxPackages -x :donkey:test -x :server:test --no-daemon - - name: Package distribution - run: tar czf openintegrationengine.tar.gz -C server/ setup --transform 's|^setup|openintegrationengine/|' + - name: Run Donkey integration tests + if: ${{ !inputs.skip_tests }} + run: ./gradlew :donkey:test --no-daemon + continue-on-error: true - - name: Create artifact + - name: Upload test results uses: actions/upload-artifact@v4 + if: always() with: - name: oie-build + name: donkey-test-results + path: donkey/build/reports/tests/test/ + if-no-files-found: ignore + + - name: List built packages + run: | + echo "=== Built Packages ===" + ls -la server/build/distributions/ + + - name: Upload RPM package + uses: actions/upload-artifact@v4 + with: + name: oie-rpm-${{ github.sha }} + path: server/build/distributions/*.rpm + if-no-files-found: error + + - name: Upload DEB package + uses: actions/upload-artifact@v4 + with: + name: oie-deb-${{ github.sha }} + path: server/build/distributions/*.deb + if-no-files-found: error + + - name: Upload tar.gz package + uses: actions/upload-artifact@v4 + with: + name: oie-tar-${{ github.sha }} + path: server/build/distributions/*.tar.gz + if-no-files-found: error + + - name: Create legacy artifact (for backward compatibility) + run: tar czf openintegrationengine.tar.gz -C server/setup . --transform 's|^|openintegrationengine/|' + + - name: Upload legacy artifact + uses: actions/upload-artifact@v4 + with: + name: oie-build-gradle path: openintegrationengine.tar.gz + + - name: Clean up signing artifacts + if: always() + run: | + rm -f server/keystore.jks server/keystore.properties + + prerelease: + name: Create Prerelease + runs-on: ubuntu-latest + needs: [build-gradle] + # Trigger on: tag push, release branch push, or manual with create_prerelease checked + if: | + startsWith(github.ref, 'refs/tags/') || + github.ref == 'refs/heads/release' || + (github.event_name == 'workflow_dispatch' && inputs.create_prerelease) + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Get version info + id: version + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + + # If triggered by a tag, use the tag name + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + TAG_NAME="${{ github.ref_name }}" + # Strip leading 'v' if present + VERSION="${TAG_NAME#v}" + PRERELEASE_TAG="${VERSION}" + else + # Extract version from build.gradle.kts - handles both formats: + # val mirthVersion by extra("4.5.2") + # val mirthVersion = "4.5.2" + VERSION=$(grep -E 'val mirthVersion' build.gradle.kts | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+[^"]*"' | tr -d '"' | head -1) + if [ -z "$VERSION" ]; then + echo "::error::Could not extract version from build.gradle.kts" + exit 1 + fi + PRERELEASE_TAG="${VERSION}-${SHORT_SHA}" + fi + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "prerelease_tag=${PRERELEASE_TAG}" >> $GITHUB_OUTPUT + echo "Building prerelease: ${PRERELEASE_TAG}" + + - name: Download RPM package + uses: actions/download-artifact@v4 + with: + name: oie-rpm-${{ github.sha }} + path: packages + + - name: Download DEB package + uses: actions/download-artifact@v4 + with: + name: oie-deb-${{ github.sha }} + path: packages + + - name: Download tar.gz package + uses: actions/download-artifact@v4 + with: + name: oie-tar-${{ github.sha }} + path: packages + + - name: Rename packages with version + run: | + cd packages + TAG="${{ steps.version.outputs.prerelease_tag }}" + for f in *.rpm *.deb *.tar.gz; do + if [ -f "$f" ]; then + # Get extension (handles .tar.gz) + case "$f" in + *.tar.gz) ext=".tar.gz"; base="${f%.tar.gz}" ;; + *) ext=".${f##*.}"; base="${f%.*}" ;; + esac + mv "$f" "oie-${TAG}${ext}" 2>/dev/null || true + fi + done + echo "=== Prerelease Packages ===" + ls -la + + - name: Delete existing prerelease if exists + run: | + gh release delete "${{ steps.version.outputs.prerelease_tag }}" --yes 2>/dev/null || true + # Only delete tag if it wasn't the trigger (avoid deleting the tag we're building from) + if [[ "${{ github.ref }}" != "refs/tags/${{ steps.version.outputs.prerelease_tag }}" ]]; then + git push --delete origin "refs/tags/${{ steps.version.outputs.prerelease_tag }}" 2>/dev/null || true + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create prerelease + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.prerelease_tag }} + name: "Prerelease ${{ steps.version.outputs.prerelease_tag }}" + prerelease: true + generate_release_notes: true + files: | + packages/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release: + name: Create Release Artifacts + runs-on: ubuntu-latest + needs: [build-gradle] + if: github.event_name == 'release' + permissions: + contents: write + + steps: + - name: Download RPM package + uses: actions/download-artifact@v4 + with: + name: oie-rpm-${{ github.sha }} + path: packages + + - name: Download DEB package + uses: actions/download-artifact@v4 + with: + name: oie-deb-${{ github.sha }} + path: packages + + - name: Download tar.gz package + uses: actions/download-artifact@v4 + with: + name: oie-tar-${{ github.sha }} + path: packages + + - name: List release artifacts + run: | + echo "=== Release Artifacts ===" + ls -la packages/ + + - name: Upload release assets + uses: softprops/action-gh-release@v2 + with: + files: | + packages/*.rpm + packages/*.deb + packages/*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 2a13c9d4b9..86e3884367 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ ############################## .mtj.tmp/ *.class +META-INF/ # Re-enable after move to MVN or Gradle #*.jar *.war @@ -98,6 +99,16 @@ Desktop.ini ############################## *.log +############################## +## Gradle Local Repository +############################## +# Generated by libs-local/install-local-deps.sh +# Only track the install script and README, not the generated JARs/POMs +libs-local/**/ +!libs-local/ +!libs-local/README.md +!libs-local/install-local-deps.sh + ############################## ## Project Specific ############################## diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..0c012353ba --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,156 @@ +import java.text.SimpleDateFormat +import java.util.Date + +plugins { + java + `java-library` + id("com.netflix.nebula.ospackage") version "11.10.1" apply false +} + +// Project-wide properties +val mirthVersion by extra("4.5.2") +val javaVersion by extra(JavaVersion.VERSION_17) + +allprojects { + group = "com.mirth.connect" + version = mirthVersion + + repositories { + mavenCentral() + // Local repository for non-Maven-Central JARs + maven { + name = "libs-local" + url = uri("${rootProject.projectDir}/libs-local") + } + flatDir { + dirs("${rootProject.projectDir}/libs-local/flat") + } + } +} + +subprojects { + apply(plugin = "java") + apply(plugin = "java-library") + + java { + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + } + + // Force resolution to use local JAR versions for dependencies not in Maven Central + configurations.all { + resolutionStrategy { + force("org.swinglabs:swingx-core:1.6.2") + force("com.jgoodies:looks:2.3.1") + force("com.sun.xml.fastinfoset:FastInfoset:1.2.13") + force("com.sun.istack:istack-commons-runtime:3.0.6") + force("javax.jws:jsr181-api:1.0") + force("org.glassfish.external:management-api:3.2.1.b001") + force("org.glassfish.gmbal:gmbal-api-only:3.1.0.b001") + force("org.glassfish.ha:ha-api:3.1.9") + force("com.sun.xml.ws:policy:2.7.2") + force("org.jvnet.mimepull:mimepull:1.9.7") + force("com.sun.xml.messaging.saaj:saaj-impl:1.0") + force("org.jvnet.staxex:stax-ex:1.8") + force("com.sun.xml.stream.buffer:streambuffer:1.5.4") + } + } + + tasks.withType().configureEach { + options.encoding = "UTF-8" + options.isDeprecation = true + options.compilerArgs.addAll(listOf( + "-Xlint:unchecked" + )) + } + + tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + } + + tasks.withType().configureEach { + useJUnit() + maxHeapSize = "2g" + jvmArgs( + "--add-exports=java.base/com.sun.crypto.provider=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens=java.base/java.text=ALL-UNNAMED", + "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens=java.desktop/java.awt=ALL-UNNAMED", + "--add-opens=java.desktop/java.awt.font=ALL-UNNAMED", + "--add-opens=java.sql/java.sql=ALL-UNNAMED", + "--add-opens=java.sql.rowset/com.sun.rowset=ALL-UNNAMED", + "--add-opens=java.sql.rowset/com.sun.rowset.internal=ALL-UNNAMED", + "--add-opens=java.sql.rowset/com.sun.rowset.providers=ALL-UNNAMED", + "--add-opens=java.sql.rowset/javax.sql.rowset=ALL-UNNAMED", + "--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED" + ) + } + + // Common test dependencies for all modules + dependencies { + testImplementation(rootProject.libs.junit) + testImplementation(rootProject.libs.mockito.core) + testImplementation(rootProject.libs.mockito.inline) + testImplementation(rootProject.libs.hamcrest) + testRuntimeOnly(rootProject.libs.byte.buddy) + testRuntimeOnly(rootProject.libs.byte.buddy.agent) + testRuntimeOnly(rootProject.libs.objenesis) + } +} + +// Create version.properties for server +tasks.register("generateVersionProperties") { + val outputDir = file("${project(":server").projectDir}/build/resources/main") + val outputFile = file("$outputDir/version.properties") + + outputs.file(outputFile) + + doLast { + outputDir.mkdirs() + val dateFormat = SimpleDateFormat("MMMM d, yyyy") + outputFile.writeText(""" + mirth.version=$mirthVersion + mirth.date=${dateFormat.format(Date())} + """.trimIndent()) + } +} + +// Task to assemble the complete setup directory +tasks.register("assembleSetup") { + group = "build" + description = "Assembles the complete setup directory with all modules" + + dependsOn( + ":donkey:build", + ":server:build", + ":client:build", + ":command:build", + ":manager:build", + ":generator:build", + ":webadmin:build", + ":server:assembleSetup" + ) +} + +// Clean all build outputs +tasks.register("cleanAll") { + group = "build" + description = "Cleans all build directories" + + dependsOn(subprojects.map { "${it.path}:clean" }) + + doLast { + delete(file("${project(":server").projectDir}/setup")) + delete(file("${project(":server").projectDir}/dist")) + } +} + +// Wrapper configuration +tasks.wrapper { + gradleVersion = "8.5" + distributionType = Wrapper.DistributionType.BIN +} diff --git a/client/.classpath b/client/.classpath index 82c3d85e47..827fc7f99e 100644 --- a/client/.classpath +++ b/client/.classpath @@ -1,217 +1,118 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/.project b/client/.project index 26fbc4505e..fbcf8fa088 100644 --- a/client/.project +++ b/client/.project @@ -1,6 +1,6 @@ - Client + client @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883972 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/client/build.gradle.kts b/client/build.gradle.kts new file mode 100644 index 0000000000..716d8fc083 --- /dev/null +++ b/client/build.gradle.kts @@ -0,0 +1,268 @@ +plugins { + `java-library` + application +} + +description = "Mirth Connect Client - GUI application" + +// Client uses traditional src/ layout +sourceSets { + main { + java { + srcDir("src") + } + resources { + srcDir("src") + include("**/*.png", "**/*.gif", "**/*.jpg", "**/*.properties", + "**/*.html", "**/*.css", "**/*.js") + } + } + test { + java { + srcDir("test") + } + } +} + +dependencies { + // Project dependencies + api(project(":donkey")) + api(project(":server")) + + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + api(libs.slf4j.api) + implementation(libs.slf4j.log4j12) + + // Apache Commons + api(libs.commons.beanutils) + api(libs.commons.codec) + api(libs.commons.collections4) + api(libs.commons.compress) + api(libs.commons.configuration2) + api(libs.commons.io) + api(libs.commons.lang3) + api(libs.commons.logging) + api(libs.commons.pool2) + api(libs.commons.text) + api(libs.commons.vfs2) + + // HTTP Client + api(libs.httpclient) + api(libs.httpcore) + api(libs.httpmime) + + // Security + api(libs.bcprov.jdk18on) + api(libs.bcpkix.jdk18on) + api(libs.bcutil.jdk18on) + + // JSON/XML + api(libs.jackson.annotations) + api(libs.jackson.core) + api(libs.jackson.databind) + api(libs.xstream) + api(libs.xpp3) + api(libs.staxon) + + // Jersey client + api(libs.jersey.client) + api(libs.jersey.common) + api(libs.jersey.proxy.client) + api(libs.jersey.media.multipart) + api(libs.jersey.guava) + api(libs.hk2.api) + api(libs.hk2.locator) + api(libs.hk2.utils) + api(libs.javax.inject) + api(libs.javax.ws.rs.api) + + // JAXB + api(libs.jaxb.api) + api(libs.jaxb.runtime) + api(libs.istack.commons.runtime) + api(libs.javax.activation) + api(libs.javax.activation.api) + api(libs.javax.annotation.api) + + // UI Libraries + api(libs.miglayout.core) + api(libs.miglayout.swing) + api(libs.swingx.core) + api(libs.rsyntaxtextarea) + api(libs.autocomplete) + api(libs.looks) + api(libs.javaparser) + api(libs.libphonenumber) + + // Utilities + api(libs.guava) + api(libs.javassist) + api(libs.joda.time) + api(libs.java.semver) + api(libs.quartz) + api(libs.velocity.engine.core) + api(libs.velocity.tools.generic) + api(libs.rhino) + api(libs.jetty.util) + api(libs.mimepull) + api(libs.reflections) + api(libs.swagger.annotations) + + // HL7/HAPI + api(libs.hapi.base) + api(libs.hapi.structures.v21) + api(libs.hapi.structures.v22) + api(libs.hapi.structures.v23) + api(libs.hapi.structures.v231) + api(libs.hapi.structures.v24) + api(libs.hapi.structures.v25) + api(libs.hapi.structures.v251) + api(libs.hapi.structures.v26) + api(libs.hapi.structures.v27) + api(libs.hapi.structures.v28) + api(libs.hapi.structures.v281) + + // AWS (for S3 file connector UI) + api(libs.aws.regions) + api(libs.aws.utils) + + // DICOM support + api(libs.dcm4che.core) + + // File connector support + api(libs.jcifs.ng) + + // Local dependencies + api(libs.wizard) + api(libs.language.support) + api(libs.openjfx.extensions) + api(libs.jai.imageio.client) + api(libs.zip4j) + + // Test + testImplementation(libs.junit) + testImplementation(libs.mockito.core) +} + +// Create mirth-client.jar +val clientJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-client") + from(sourceSets.main.get().output) +} + +application { + mainClass.set("com.mirth.connect.client.ui.Mirth") +} + +// ============================================================================= +// Extension Client JARs +// ============================================================================= + +val extClientBuildDir = file("$buildDir/extensionClients") + +// Define extension client configurations: name to source package path +val extensionClientConfigs = mapOf( + // Connectors + "dicom" to "com/mirth/connect/connectors/dimse", + "doc" to "com/mirth/connect/connectors/doc", + "file" to "com/mirth/connect/connectors/file", + "http" to "com/mirth/connect/connectors/http", + "jdbc" to "com/mirth/connect/connectors/jdbc", + "jms" to "com/mirth/connect/connectors/jms", + "js" to "com/mirth/connect/connectors/js", + "smtp" to "com/mirth/connect/connectors/smtp", + "tcp" to "com/mirth/connect/connectors/tcp", + "vm" to "com/mirth/connect/connectors/vm", + "ws" to "com/mirth/connect/connectors/ws", + // Datatypes + "datatype-delimited" to "com/mirth/connect/plugins/datatypes/delimited", + "datatype-dicom" to "com/mirth/connect/plugins/datatypes/dicom", + "datatype-edi" to "com/mirth/connect/plugins/datatypes/edi", + "datatype-hl7v2" to "com/mirth/connect/plugins/datatypes/hl7v2", + "datatype-hl7v3" to "com/mirth/connect/plugins/datatypes/hl7v3", + "datatype-json" to "com/mirth/connect/plugins/datatypes/json", + "datatype-ncpdp" to "com/mirth/connect/plugins/datatypes/ncpdp", + "datatype-raw" to "com/mirth/connect/plugins/datatypes/raw", + "datatype-xml" to "com/mirth/connect/plugins/datatypes/xml", + // Plugins + "directoryresource" to "com/mirth/connect/plugins/directoryresource", + "dashboardstatus" to "com/mirth/connect/plugins/dashboardstatus", + "destinationsetfilter" to "com/mirth/connect/plugins/destinationsetfilter", + "dicomviewer" to "com/mirth/connect/plugins/dicomviewer", + "globalmapviewer" to "com/mirth/connect/plugins/globalmapviewer", + "httpauth" to "com/mirth/connect/plugins/httpauth", + "imageviewer" to "com/mirth/connect/plugins/imageviewer", + "javascriptrule" to "com/mirth/connect/plugins/javascriptrule", + "javascriptstep" to "com/mirth/connect/plugins/javascriptstep", + "mapper" to "com/mirth/connect/plugins/mapper", + "messagebuilder" to "com/mirth/connect/plugins/messagebuilder", + "datapruner" to "com/mirth/connect/plugins/datapruner", + "mllpmode" to "com/mirth/connect/plugins/mllpmode", + "pdfviewer" to "com/mirth/connect/plugins/pdfviewer", + "textviewer" to "com/mirth/connect/plugins/textviewer", + "rulebuilder" to "com/mirth/connect/plugins/rulebuilder", + "serverlog" to "com/mirth/connect/plugins/serverlog", + "scriptfilerule" to "com/mirth/connect/plugins/scriptfilerule", + "scriptfilestep" to "com/mirth/connect/plugins/scriptfilestep", + "xsltstep" to "com/mirth/connect/plugins/xsltstep" +) + +// Create client JAR tasks for each extension +extensionClientConfigs.forEach { (extName, srcPath) -> + tasks.register("${extName}ClientJar") { + archiveBaseName.set("$extName-client") + destinationDirectory.set(file("$extClientBuildDir/$extName")) + + from(sourceSets.main.get().output) { + include("$srcPath/**") + } + } +} + +// Task to build all extension client JARs +val buildExtensionClients by tasks.registering { + group = "build" + description = "Builds all extension client JARs" + + extensionClientConfigs.keys.forEach { extName -> + dependsOn("${extName}ClientJar") + } +} + +// Task to copy client JARs to server's extension directories +val installExtensionClients by tasks.registering { + group = "build" + description = "Installs extension client JARs to server extensions directory" + + dependsOn(buildExtensionClients) + + doLast { + val serverExtensionsDir = project(":server").file("setup/extensions") + + extensionClientConfigs.keys.forEach { extName -> + val extDir = file("$serverExtensionsDir/$extName") + if (extDir.exists()) { + copy { + from("$extClientBuildDir/$extName") { + include("*-client-*.jar") + } + into(extDir) + rename { "$extName-client.jar" } + } + } + } + + logger.lifecycle("Installed ${extensionClientConfigs.size} extension client JARs") + } +} + +tasks.named("assemble") { + dependsOn(clientJar, buildExtensionClients) +} + +artifacts { + add("archives", clientJar) +} diff --git a/command/.classpath b/command/.classpath index 201d91e87d..d25b63fe4c 100644 --- a/command/.classpath +++ b/command/.classpath @@ -1,94 +1,64 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/command/.project b/command/.project index e0884d3e51..327cbe64ec 100644 --- a/command/.project +++ b/command/.project @@ -1,6 +1,6 @@ - Command + command @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883975 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/command/build.gradle.kts b/command/build.gradle.kts new file mode 100644 index 0000000000..f2520eee0f --- /dev/null +++ b/command/build.gradle.kts @@ -0,0 +1,105 @@ +plugins { + `java-library` + application +} + +description = "Mirth Connect CLI - Command Line Interface" + +// Command uses traditional src/ layout +sourceSets { + main { + java { + srcDir("src") + } + resources { + srcDir("conf") + } + } + test { + java { + srcDir("test") + } + } +} + +dependencies { + // Project dependencies + api(project(":donkey")) + api(project(":server")) + + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + api(libs.slf4j.api) + implementation(libs.slf4j.log4j12) + + // Apache Commons + api(libs.commons.cli) + api(libs.commons.codec) + api(libs.commons.collections4) + api(libs.commons.configuration2) + api(libs.commons.io) + api(libs.commons.lang3) + api(libs.commons.logging) + api(libs.commons.pool2) + api(libs.commons.vfs2) + + // HTTP Client + api(libs.httpclient) + api(libs.httpcore) + api(libs.httpmime) + + // Security + api(libs.bcprov.jdk18on) + api(libs.bcpkix.jdk18on) + api(libs.bcutil.jdk18on) + + // JSON/XML + api(libs.xstream) + api(libs.xpp3) + + // Jetty (for HTTP utilities) + api(libs.jetty.util) + + // Utilities + api(libs.velocity.engine.core) + api(libs.velocity.tools.generic) + api(libs.rhino) + + // Test + testImplementation(libs.junit) +} + +// Create mirth-cli.jar +val cliJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-cli") + from(sourceSets.main.get().output) +} + +// Create mirth-cli-launcher.jar with manifest +val cliLauncherJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-cli-launcher") + from(sourceSets.main.get().output) { + include("com/mirth/connect/cli/launcher/**") + } + manifest { + attributes( + "Main-Class" to "com.mirth.connect.cli.launcher.CommandLineLauncher", + "Class-Path" to "cli-lib/" + ) + } +} + +application { + mainClass.set("com.mirth.connect.cli.CommandLineInterface") +} + +tasks.named("assemble") { + dependsOn(cliJar, cliLauncherJar) +} + +artifacts { + add("archives", cliJar) + add("archives", cliLauncherJar) +} diff --git a/donkey/.classpath b/donkey/.classpath index 3595e9105e..f3de61aeaa 100644 --- a/donkey/.classpath +++ b/donkey/.classpath @@ -1,88 +1,33 @@ - - - - - - - + - + + - + - + + + - - - + - + + + - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/donkey/.project b/donkey/.project index e2a5d1be04..d5a530316a 100644 --- a/donkey/.project +++ b/donkey/.project @@ -1,6 +1,6 @@ - Donkey + donkey @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883976 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/donkey/build.gradle.kts b/donkey/build.gradle.kts new file mode 100644 index 0000000000..7e324468d9 --- /dev/null +++ b/donkey/build.gradle.kts @@ -0,0 +1,136 @@ +plugins { + `java-library` +} + +description = "Donkey - Message routing and transformation engine" + +// Donkey uses Maven-style source layout +sourceSets { + main { + java { + srcDir("src/main/java") + } + resources { + srcDirs("conf", "donkeydbconf") + } + } + test { + java { + srcDir("src/test/java") + } + resources { + // Tests need access to donkey-testing.properties and database configs + srcDirs("conf", "donkeydbconf") + } + } +} + +dependencies { + // Apache Commons + api(libs.commons.beanutils) + api(libs.commons.codec) + api(libs.commons.collections4) + api(libs.commons.dbcp2) + api(libs.commons.dbutils) + api(libs.commons.io) + api(libs.commons.lang3) + api(libs.commons.logging) + api(libs.commons.math3) + api(libs.commons.pool2) + api(libs.commons.text) + + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + api(libs.slf4j.api) + implementation(libs.slf4j.log4j12) + + // Database + api(libs.hikaricp) + api(libs.quartz) + implementation(libs.derby) + implementation(libs.jtds) + implementation(libs.mysql.connector) + implementation(libs.mssql.jdbc) + implementation(libs.postgresql) + implementation(libs.ojdbc8) + + // DI/IoC + api(libs.guice) + implementation(libs.aopalliance.repackaged) + implementation(libs.javax.inject) + + // XML/Serialization + api(libs.xstream) + api(libs.xpp3) + + // Utilities + api(libs.javassist) + api(libs.guava) + implementation(libs.failureaccess) + implementation(libs.checker.qual) + implementation(libs.error.prone.annotations) + implementation(libs.j2objc.annotations) + implementation(libs.jsr305) + implementation(libs.listenablefuture) + + // Test dependencies + testImplementation(libs.junit) + testImplementation(libs.mockito.core) + testRuntimeOnly(libs.byte.buddy) + testRuntimeOnly(libs.byte.buddy.agent) + testRuntimeOnly(libs.objenesis) +} + +// Create the donkey-model.jar with model classes +val donkeyModelJar by tasks.registering(Jar::class) { + archiveBaseName.set("donkey-model") + from(sourceSets.main.get().output) { + include("com/mirth/connect/donkey/model/**") + include("com/mirth/connect/donkey/util/**") + } +} + +// Create the donkey-server.jar with server classes +val donkeyServerJar by tasks.registering(Jar::class) { + archiveBaseName.set("donkey-server") + from(sourceSets.main.get().output) { + include("com/mirth/connect/donkey/server/**") + } +} + +// Create the donkey dbconf jar +val donkeyDbconfJar by tasks.registering(Jar::class) { + archiveBaseName.set("donkey-dbconf") + from("donkeydbconf") +} + +// Make sure custom JARs are built +tasks.named("assemble") { + dependsOn(donkeyModelJar, donkeyServerJar, donkeyDbconfJar) +} + +// Publish artifacts for other modules to consume +artifacts { + add("archives", donkeyModelJar) + add("archives", donkeyServerJar) + add("archives", donkeyDbconfJar) +} + +// Configuration for other modules to depend on specific JARs +configurations { + create("model") { + isCanBeConsumed = true + isCanBeResolved = false + } + create("server") { + isCanBeConsumed = true + isCanBeResolved = false + } +} + +artifacts { + add("model", donkeyModelJar) + add("server", donkeyServerJar) +} diff --git a/donkey/conf/donkey-testing.properties b/donkey/conf/donkey-testing.properties index 9dd552cde5..a38fc72eda 100644 --- a/donkey/conf/donkey-testing.properties +++ b/donkey/conf/donkey-testing.properties @@ -11,8 +11,8 @@ database = postgres # SQLServer jdbc:jtds:sqlserver://localhost:1433/mirthdb database.url = jdbc:postgresql://localhost:5432/donkeytest -# if using a custom driver, specify it here -#database.driver = +# PostgreSQL driver for HikariCP +database.driver = org.postgresql.Driver # database credentials database.username = postgres diff --git a/donkey/src/test/java/com/mirth/connect/donkey/test/util/TestUtils.java b/donkey/src/test/java/com/mirth/connect/donkey/test/util/TestUtils.java index 23d59d5334..cb2b406208 100644 --- a/donkey/src/test/java/com/mirth/connect/donkey/test/util/TestUtils.java +++ b/donkey/src/test/java/com/mirth/connect/donkey/test/util/TestUtils.java @@ -61,6 +61,7 @@ import com.mirth.connect.donkey.model.message.attachment.Attachment; import com.mirth.connect.donkey.server.Donkey; import com.mirth.connect.donkey.server.DonkeyConfiguration; +import com.mirth.connect.donkey.server.DonkeyConnectionPools; import com.mirth.connect.donkey.server.channel.Channel; import com.mirth.connect.donkey.server.channel.DestinationChainProvider; import com.mirth.connect.donkey.server.channel.DestinationConnector; @@ -1291,6 +1292,9 @@ public static DonkeyConfiguration getDonkeyTestConfiguration() { databaseProperties.load(is); IOUtils.closeQuietly(is); + // Initialize connection pools before returning configuration + DonkeyConnectionPools.getInstance().init(databaseProperties); + return new DonkeyConfiguration(new File(".").getAbsolutePath(), databaseProperties, null, new EventDispatcher() { @Override public void dispatchEvent(Event event) {} diff --git a/generator/.classpath b/generator/.classpath index f7f903c27c..f7964ac30a 100644 --- a/generator/.classpath +++ b/generator/.classpath @@ -1,28 +1,19 @@ - - - - - - - + - + + - + - + + + - - - - - - - - - + + + diff --git a/generator/.project b/generator/.project index 2c663857ca..f6240c1877 100644 --- a/generator/.project +++ b/generator/.project @@ -1,6 +1,6 @@ - Generator + generator @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883977 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/generator/build.gradle.kts b/generator/build.gradle.kts new file mode 100644 index 0000000000..066e54c686 --- /dev/null +++ b/generator/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + `java-library` + application +} + +description = "Mirth Connect Generator - Data model generator" + +// Generator uses traditional src/ layout with embedded test folder +sourceSets { + main { + java { + srcDir("src") + exclude("**/test/**") + } + } + test { + java { + srcDir("src/com/mirth/connect/model/generator/test") + } + } +} + +dependencies { + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + api(libs.slf4j.api) + implementation(libs.slf4j.log4j12) + + // Apache Commons + api(libs.commons.collections4) + api(libs.commons.io) + api(libs.commons.lang3) + + // Template engine + api(libs.velocity.engine.core) + + // Test + testImplementation(libs.junit) + testRuntimeOnly(libs.mirth.vocab) +} + +// Create model-generator.jar +val modelGeneratorJar by tasks.registering(Jar::class) { + archiveBaseName.set("model-generator") + from(sourceSets.main.get().output) +} + +// Create mirth-vocab jar (if generated) +val mirthVocabJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-vocab") + archiveVersion.set("1.2") + // This would include generated vocabulary classes + from(sourceSets.main.get().output) { + include("com/mirth/connect/model/vocab/**") + } +} + +application { + mainClass.set("com.mirth.connect.model.generator.ModelGenerator") +} + +tasks.named("assemble") { + dependsOn(modelGeneratorJar) +} + +artifacts { + add("archives", modelGeneratorJar) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..ed953adc17 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,536 @@ +[versions] +# Core versions +mirth = "4.5.2" +java = "17" + +# Apache Commons +commons-beanutils = "1.9.4" +commons-cli = "1.2" +commons-codec = "1.16.0" +commons-collections = "3.2.2" +commons-collections4 = "4.4" +commons-compress = "1.24.0" +commons-configuration2 = "2.8.0" +commons-dbcp2 = "2.0.1" +commons-dbutils = "1.7" +commons-digester3 = "3.2" +commons-el = "1.0" +commons-email = "1.6.0" +commons-fileupload = "1.5" +commons-httpclient-legacy = "3.0.1" +commons-io = "2.13.0" +commons-jxpath = "1.3" +commons-lang3 = "3.20.0" +commons-logging = "1.2" +commons-math3 = "3.0" +commons-net = "3.9.0" +commons-pool2 = "2.3" +commons-text = "1.15.0" +commons-vfs2 = "2.10.0" + +# Apache HTTP Client +httpclient = "4.5.13" +httpcore = "4.4.13" + +# Logging +log4j = "2.17.2" +slf4j = "1.7.30" + +# Security/Crypto +bouncycastle = "1.78.1" + +# JSON/XML Processing +jackson = "2.14.3" +xstream = "1.4.20" +xpp3 = "1.1.4c" +staxon = "1.3" +jdom2 = "2.0.6.1" + +# Database +hikaricp = "2.5.1" +mybatis = "3.1.1" +derby = "10.10.2.0" +jtds = "1.3.1" +sqlite-jdbc = "3.43.2.1" +mysql-connector = "8.4.0" +mssql-jdbc = "8.4.1.jre8" +postgresql = "42.6.0" +ojdbc8 = "12.2.0.1" + +# Jetty +jetty = "9.4.57.v20241219" +jetty-schemas = "3.1.2" +jasper = "8.5.70" +taglibs = "1.2.5" +ecj = "3.19.0" + +# Jersey/JAX-RS +jersey = "2.22.1" +hk2 = "2.4.0-b31" + +# javax/jakarta +javax-servlet = "3.1.0" +javax-inject = "2.4.0-b31" +javax-json = "1.0.4" +javax-json-api = "1.0" +javax-mail = "1.5.0" +javax-ws-rs = "2.0.1" +javax-activation = "1.2.0" +javax-annotation = "1.3.2" +validation-api = "1.1.0.Final" +persistence-api = "1.0" + +# JAXB +jaxb-api = "2.4.0-b180725.0427" +jaxb-runtime = "2.4.0-b180725.0644" +istack-commons = "3.0.6" +txw2 = "2.4.0-b180725.0644" + +# JAX-WS +jaxws-api = "2.3.0" +jaxws-rt = "2.3.0.2" +jaxws-tools = "2.3.0.2" +javax-xml-soap = "1.4.0" +fastinfoset = "1.2.13" +# Note: FastInfoset 1.2.14 is requested by jaxb-runtime but we have 1.2.13 +gmbal = "3.1.0.b001" +ha-api = "3.1.9" +jsr181-api = "1.0" +management-api = "3.2.1.b001" +mimepull = "1.9.7" +policy = "2.7.2" +saaj-impl = "1.0" +stax-ex = "1.8" +streambuffer = "1.5.4" + +# Swagger +swagger = "2.0.10" +reflections = "0.9.10" +classgraph = "4.8.179" + +# Google Guava +guava = "32.0.1-jre" +failureaccess = "1.0.1" +checker-qual = "2.10.0" +error-prone = "2.3.4" +j2objc-annotations = "1.3" +jsr305 = "3.0.2" +listenablefuture = "9999.0-empty-to-avoid-conflict-with-guava" + +# Guice +guice = "4.1.0" +aopalliance = "2.4.0-b31" + +# ASM +asm = "9.6" + +# Other utilities +javassist = "3.26.0-GA" +joda-time = "2.9.9" +java-semver = "0.10.2" +quartz = "2.3.2" +velocity-engine = "2.3" +velocity-tools = "3.1" +rhino = "1.7.13" +jsch = "2.27.7" +jna = "4.5.2" +oshi-core = "3.9.1" +backport-util = "3.1" + +# JMS +geronimo-jms = "1.1.1" +geronimo-j2ee = "1.0.1" + +# OSGi +osgi-core = "4.2.0" +osgi-resource-locator = "1.0.1" + +# HL7/HAPI +hapi = "2.3" + +# DICOM/DCM4CHE +dcm4che = "2.0.29" + +# PDF/Document processing +flying-saucer = "9.0.1" +itext = "2.1.7" +openhtmltopdf = "1.0.9" +pdfbox = "2.0.24" + +# AWS SDK +aws-sdk = "2.15.28" +netty = "4.1.119.Final" +netty-nio-client = "2.20.140" +netty-reactive = "2.0.8" +reactive-streams = "1.0.3" +eventstream = "1.0.1" + +# GUI/UI +miglayout = "4.2" +swingx-core = "1.6.2" +rsyntaxtextarea = "2.5.6" +autocomplete = "2.5.4" +looks = "2.3.1" +javaparser = "1.0.8" +libphonenumber = "8.12.50" + +# Web/Stripes +displaytag = "1.2" +json-simple = "1.1.1" + +# Testing +junit = "4.13.1" +mockito = "5.1.1" +hamcrest = "2.2" +byte-buddy = "1.14.13" +objenesis = "2.5.1" + +# File handling +zip4j = "1.3.3" +jcifs-ng = "2.1.10" + +# Local/Non-Maven-Central dependencies +mirth-vocab = "1.0" +not-yet-commons-ssl = "0.3.18" +jai-imageio = "1.1" +wsdl4j-fixed = "1.6.2" +imagej = "1.53" +pdfrenderer = "0.9.1" +webdavclient4j = "0.92" +wizard = "1.0" +language-support = "1.0" +openjfx-extensions = "1.0" +stripes = "1.6.0" + +[libraries] +# Apache Commons +commons-beanutils = { module = "commons-beanutils:commons-beanutils", version.ref = "commons-beanutils" } +commons-cli = { module = "commons-cli:commons-cli", version.ref = "commons-cli" } +commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" } +commons-collections = { module = "commons-collections:commons-collections", version.ref = "commons-collections" } +commons-collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "commons-collections4" } +commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commons-compress" } +commons-configuration2 = { module = "org.apache.commons:commons-configuration2", version.ref = "commons-configuration2" } +commons-dbcp2 = { module = "org.apache.commons:commons-dbcp2", version.ref = "commons-dbcp2" } +commons-dbutils = { module = "commons-dbutils:commons-dbutils", version.ref = "commons-dbutils" } +commons-digester3 = { module = "org.apache.commons:commons-digester3", version.ref = "commons-digester3" } +commons-el = { module = "commons-el:commons-el", version.ref = "commons-el" } +commons-email = { module = "org.apache.commons:commons-email", version.ref = "commons-email" } +commons-fileupload = { module = "commons-fileupload:commons-fileupload", version.ref = "commons-fileupload" } +commons-httpclient-legacy = { module = "commons-httpclient:commons-httpclient", version.ref = "commons-httpclient-legacy" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } +commons-jxpath = { module = "commons-jxpath:commons-jxpath", version.ref = "commons-jxpath" } +commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" } +commons-logging = { module = "commons-logging:commons-logging", version.ref = "commons-logging" } +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +commons-net = { module = "commons-net:commons-net", version.ref = "commons-net" } +commons-pool2 = { module = "org.apache.commons:commons-pool2", version.ref = "commons-pool2" } +commons-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" } +commons-vfs2 = { module = "org.apache.commons:commons-vfs2", version.ref = "commons-vfs2" } + +# Apache HTTP Client +httpclient = { module = "org.apache.httpcomponents:httpclient", version.ref = "httpclient" } +httpcore = { module = "org.apache.httpcomponents:httpcore", version.ref = "httpcore" } +httpmime = { module = "org.apache.httpcomponents:httpmime", version.ref = "httpclient" } + +# Logging +log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j-bridge = { module = "org.apache.logging.log4j:log4j-1.2-api", version.ref = "log4j" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-log4j12 = { module = "org.slf4j:slf4j-log4j12", version.ref = "slf4j" } + +# Security/Crypto +bcprov-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" } +bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncycastle" } +bcutil-jdk18on = { module = "org.bouncycastle:bcutil-jdk18on", version.ref = "bouncycastle" } + +# JSON/XML Processing +jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" } +jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-dataformat-cbor = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor", version.ref = "jackson" } +jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } +jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" } +xstream = { module = "com.thoughtworks.xstream:xstream", version.ref = "xstream" } +xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" } +staxon = { module = "de.odysseus.staxon:staxon", version.ref = "staxon" } +jdom2 = { module = "org.jdom:jdom2", version.ref = "jdom2" } + +# Database +hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" } +mybatis = { module = "org.mybatis:mybatis", version.ref = "mybatis" } +derby = { module = "org.apache.derby:derby", version.ref = "derby" } +derbytools = { module = "org.apache.derby:derbytools", version.ref = "derby" } +jtds = { module = "net.sourceforge.jtds:jtds", version.ref = "jtds" } +sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" } +mysql-connector = { module = "com.mysql:mysql-connector-j", version.ref = "mysql-connector" } +mssql-jdbc = { module = "com.microsoft.sqlserver:mssql-jdbc", version.ref = "mssql-jdbc" } +postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } +ojdbc8 = { module = "com.oracle.database.jdbc:ojdbc8", version.ref = "ojdbc8" } + +# Jetty +jetty-annotations = { module = "org.eclipse.jetty:jetty-annotations", version.ref = "jetty" } +jetty-continuation = { module = "org.eclipse.jetty:jetty-continuation", version.ref = "jetty" } +jetty-http = { module = "org.eclipse.jetty:jetty-http", version.ref = "jetty" } +jetty-io = { module = "org.eclipse.jetty:jetty-io", version.ref = "jetty" } +jetty-jndi = { module = "org.eclipse.jetty:jetty-jndi", version.ref = "jetty" } +jetty-plus = { module = "org.eclipse.jetty:jetty-plus", version.ref = "jetty" } +jetty-rewrite = { module = "org.eclipse.jetty:jetty-rewrite", version.ref = "jetty" } +jetty-security = { module = "org.eclipse.jetty:jetty-security", version.ref = "jetty" } +jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "jetty" } +jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "jetty" } +jetty-util = { module = "org.eclipse.jetty:jetty-util", version.ref = "jetty" } +jetty-util-ajax = { module = "org.eclipse.jetty:jetty-util-ajax", version.ref = "jetty" } +jetty-webapp = { module = "org.eclipse.jetty:jetty-webapp", version.ref = "jetty" } +jetty-xml = { module = "org.eclipse.jetty:jetty-xml", version.ref = "jetty" } +jetty-schemas = { module = "org.eclipse.jetty.toolchain:jetty-schemas", version.ref = "jetty-schemas" } +apache-jsp = { module = "org.eclipse.jetty:apache-jsp", version.ref = "jetty" } +taglibs-standard-impl = { module = "org.apache.taglibs:taglibs-standard-impl", version.ref = "taglibs" } +taglibs-standard-spec = { module = "org.apache.taglibs:taglibs-standard-spec", version.ref = "taglibs" } +mortbay-apache-el = { module = "org.mortbay.jasper:apache-el", version.ref = "jasper" } +mortbay-apache-jsp = { module = "org.mortbay.jasper:apache-jsp", version.ref = "jasper" } +ecj = { module = "org.eclipse.jdt:ecj", version.ref = "ecj" } + +# Jersey/JAX-RS +jersey-client = { module = "org.glassfish.jersey.core:jersey-client", version.ref = "jersey" } +jersey-common = { module = "org.glassfish.jersey.core:jersey-common", version.ref = "jersey" } +jersey-server = { module = "org.glassfish.jersey.core:jersey-server", version.ref = "jersey" } +jersey-container-servlet = { module = "org.glassfish.jersey.containers:jersey-container-servlet", version.ref = "jersey" } +jersey-container-servlet-core = { module = "org.glassfish.jersey.containers:jersey-container-servlet-core", version.ref = "jersey" } +jersey-container-jetty-http = { module = "org.glassfish.jersey.containers:jersey-container-jetty-http", version.ref = "jersey" } +jersey-container-jetty-servlet = { module = "org.glassfish.jersey.containers:jersey-container-jetty-servlet", version.ref = "jersey" } +jersey-media-jaxb = { module = "org.glassfish.jersey.media:jersey-media-jaxb", version.ref = "jersey" } +jersey-media-multipart = { module = "org.glassfish.jersey.media:jersey-media-multipart", version.ref = "jersey" } +jersey-proxy-client = { module = "org.glassfish.jersey.ext:jersey-proxy-client", version.ref = "jersey" } +jersey-guava = { module = "org.glassfish.jersey.bundles.repackaged:jersey-guava", version.ref = "jersey" } +hk2-api = { module = "org.glassfish.hk2:hk2-api", version.ref = "hk2" } +hk2-locator = { module = "org.glassfish.hk2:hk2-locator", version.ref = "hk2" } +hk2-utils = { module = "org.glassfish.hk2:hk2-utils", version.ref = "hk2" } +aopalliance-repackaged = { module = "org.glassfish.hk2.external:aopalliance-repackaged", version.ref = "aopalliance" } + +# javax/jakarta +javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version.ref = "javax-servlet" } +javax-inject = { module = "org.glassfish.hk2.external:javax.inject", version.ref = "javax-inject" } +javax-json = { module = "org.glassfish:javax.json", version.ref = "javax-json" } +javax-json-api = { module = "javax.json:javax.json-api", version.ref = "javax-json-api" } +javax-mail = { module = "javax.mail:javax.mail-api", version.ref = "javax-mail" } +javax-ws-rs-api = { module = "javax.ws.rs:javax.ws.rs-api", version.ref = "javax-ws-rs" } +javax-activation = { module = "com.sun.activation:javax.activation", version.ref = "javax-activation" } +javax-activation-api = { module = "javax.activation:javax.activation-api", version.ref = "javax-activation" } +javax-annotation-api = { module = "javax.annotation:javax.annotation-api", version.ref = "javax-annotation" } +validation-api = { module = "javax.validation:validation-api", version.ref = "validation-api" } +persistence-api = { module = "javax.persistence:persistence-api", version.ref = "persistence-api" } + +# JAXB +jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb-api" } +jaxb-runtime = { module = "org.glassfish.jaxb:jaxb-runtime", version.ref = "jaxb-runtime" } +istack-commons-runtime = { module = "com.sun.istack:istack-commons-runtime", version.ref = "istack-commons" } +txw2 = { module = "org.glassfish.jaxb:txw2", version.ref = "txw2" } + +# JAX-WS +jaxws-api = { module = "javax.xml.ws:jaxws-api", version.ref = "jaxws-api" } +jaxws-rt = { module = "com.sun.xml.ws:jaxws-rt", version.ref = "jaxws-rt" } +jaxws-tools = { module = "com.sun.xml.ws:jaxws-tools", version.ref = "jaxws-tools" } +javax-xml-soap-api = { module = "javax.xml.soap:javax.xml.soap-api", version.ref = "javax-xml-soap" } +fastinfoset = { module = "com.sun.xml.fastinfoset:FastInfoset", version.ref = "fastinfoset" } +gmbal-api-only = { module = "org.glassfish.gmbal:gmbal-api-only", version.ref = "gmbal" } +ha-api = { module = "org.glassfish.ha:ha-api", version.ref = "ha-api" } +jsr181-api = { module = "javax.jws:jsr181-api", version.ref = "jsr181-api" } +management-api = { module = "org.glassfish.external:management-api", version.ref = "management-api" } +mimepull = { module = "org.jvnet.mimepull:mimepull", version.ref = "mimepull" } +policy = { module = "com.sun.xml.ws:policy", version.ref = "policy" } +saaj-impl = { module = "com.sun.xml.messaging.saaj:saaj-impl", version.ref = "saaj-impl" } +stax-ex = { module = "org.jvnet.staxex:stax-ex", version.ref = "stax-ex" } +streambuffer = { module = "com.sun.xml.stream.buffer:streambuffer", version.ref = "streambuffer" } + +# Swagger +swagger-annotations = { module = "io.swagger.core.v3:swagger-annotations", version.ref = "swagger" } +swagger-core = { module = "io.swagger.core.v3:swagger-core", version.ref = "swagger" } +swagger-jaxrs2 = { module = "io.swagger.core.v3:swagger-jaxrs2", version.ref = "swagger" } +swagger-models = { module = "io.swagger.core.v3:swagger-models", version.ref = "swagger" } +swagger-integration = { module = "io.swagger.core.v3:swagger-integration", version.ref = "swagger" } +swagger-jaxrs2-servlet-initializer = { module = "io.swagger.core.v3:swagger-jaxrs2-servlet-initializer", version.ref = "swagger" } +reflections = { module = "org.reflections:reflections", version.ref = "reflections" } +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } + +# Google Guava +guava = { module = "com.google.guava:guava", version.ref = "guava" } +failureaccess = { module = "com.google.guava:failureaccess", version.ref = "failureaccess" } +checker-qual = { module = "org.checkerframework:checker-qual", version.ref = "checker-qual" } +error-prone-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "error-prone" } +j2objc-annotations = { module = "com.google.j2objc:j2objc-annotations", version.ref = "j2objc-annotations" } +jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } +listenablefuture = { module = "com.google.guava:listenablefuture", version.ref = "listenablefuture" } + +# Guice +guice = { module = "com.google.inject:guice", version.ref = "guice" } + +# ASM +asm = { module = "org.ow2.asm:asm", version.ref = "asm" } +asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" } +asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } +asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } +asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } + +# Other utilities +javassist = { module = "org.javassist:javassist", version.ref = "javassist" } +joda-time = { module = "joda-time:joda-time", version.ref = "joda-time" } +java-semver = { module = "com.github.zafarkhaja:java-semver", version.ref = "java-semver" } +quartz = { module = "org.quartz-scheduler:quartz", version.ref = "quartz" } +velocity-engine-core = { module = "org.apache.velocity:velocity-engine-core", version.ref = "velocity-engine" } +velocity-tools-generic = { module = "org.apache.velocity.tools:velocity-tools-generic", version.ref = "velocity-tools" } +rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } +jsch = { module = "com.github.mwiede:jsch", version.ref = "jsch" } +jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } +jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } +oshi-core = { module = "com.github.oshi:oshi-core", version.ref = "oshi-core" } +backport-util-concurrent = { module = "backport-util-concurrent:backport-util-concurrent-java60", version = "3.1" } + +# JMS +geronimo-jms = { module = "org.apache.geronimo.specs:geronimo-jms_1.1_spec", version.ref = "geronimo-jms" } +geronimo-j2ee-management = { module = "org.apache.geronimo.specs:geronimo-j2ee-management_1.1_spec", version.ref = "geronimo-j2ee" } + +# OSGi +osgi-core = { module = "org.osgi:org.osgi.core", version.ref = "osgi-core" } +osgi-resource-locator = { module = "org.glassfish.hk2:osgi-resource-locator", version.ref = "osgi-resource-locator" } + +# HL7/HAPI +hapi-base = { module = "ca.uhn.hapi:hapi-base", version.ref = "hapi" } +hapi-structures-v21 = { module = "ca.uhn.hapi:hapi-structures-v21", version.ref = "hapi" } +hapi-structures-v22 = { module = "ca.uhn.hapi:hapi-structures-v22", version.ref = "hapi" } +hapi-structures-v23 = { module = "ca.uhn.hapi:hapi-structures-v23", version.ref = "hapi" } +hapi-structures-v231 = { module = "ca.uhn.hapi:hapi-structures-v231", version.ref = "hapi" } +hapi-structures-v24 = { module = "ca.uhn.hapi:hapi-structures-v24", version.ref = "hapi" } +hapi-structures-v25 = { module = "ca.uhn.hapi:hapi-structures-v25", version.ref = "hapi" } +hapi-structures-v251 = { module = "ca.uhn.hapi:hapi-structures-v251", version.ref = "hapi" } +hapi-structures-v26 = { module = "ca.uhn.hapi:hapi-structures-v26", version.ref = "hapi" } +hapi-structures-v27 = { module = "ca.uhn.hapi:hapi-structures-v27", version.ref = "hapi" } +hapi-structures-v28 = { module = "ca.uhn.hapi:hapi-structures-v28", version.ref = "hapi" } +hapi-structures-v281 = { module = "ca.uhn.hapi:hapi-structures-v281", version.ref = "hapi" } + +# DICOM/DCM4CHE +dcm4che-core = { module = "dcm4che:dcm4che-core", version.ref = "dcm4che" } +dcm4che-filecache = { module = "dcm4che:dcm4che-filecache", version.ref = "dcm4che" } +dcm4che-net = { module = "dcm4che:dcm4che-net", version.ref = "dcm4che" } +dcm4che-tool-dcmrcv = { module = "dcm4che:dcm4che-tool-dcmrcv", version.ref = "dcm4che" } +dcm4che-tool-dcmsnd = { module = "dcm4che:dcm4che-tool-dcmsnd", version.ref = "dcm4che" } + +# PDF/Document processing +flying-saucer-core = { module = "org.xhtmlrenderer:flying-saucer-core", version.ref = "flying-saucer" } +flying-saucer-pdf = { module = "org.xhtmlrenderer:flying-saucer-pdf", version.ref = "flying-saucer" } +itext = { module = "com.lowagie:itext", version.ref = "itext" } +itext-rtf = { module = "com.lowagie:itext-rtf", version.ref = "itext" } +openhtmltopdf-core = { module = "com.openhtmltopdf:openhtmltopdf-core", version.ref = "openhtmltopdf" } +openhtmltopdf-pdfbox = { module = "com.openhtmltopdf:openhtmltopdf-pdfbox", version.ref = "openhtmltopdf" } +pdfbox = { module = "org.apache.pdfbox:pdfbox", version.ref = "pdfbox" } +fontbox = { module = "org.apache.pdfbox:fontbox", version.ref = "pdfbox" } +xmpbox = { module = "org.apache.pdfbox:xmpbox", version.ref = "pdfbox" } +graphics2d = { module = "de.rototor.pdfbox:graphics2d", version = "0.32" } + +# AWS SDK +aws-annotations = { module = "software.amazon.awssdk:annotations", version.ref = "aws-sdk" } +aws-apache-client = { module = "software.amazon.awssdk:apache-client", version.ref = "aws-sdk" } +aws-auth = { module = "software.amazon.awssdk:auth", version.ref = "aws-sdk" } +aws-core = { module = "software.amazon.awssdk:aws-core", version.ref = "aws-sdk" } +aws-json-protocol = { module = "software.amazon.awssdk:aws-json-protocol", version.ref = "aws-sdk" } +aws-query-protocol = { module = "software.amazon.awssdk:aws-query-protocol", version.ref = "aws-sdk" } +aws-xml-protocol = { module = "software.amazon.awssdk:aws-xml-protocol", version.ref = "aws-sdk" } +aws-http-client-spi = { module = "software.amazon.awssdk:http-client-spi", version.ref = "aws-sdk" } +aws-kms = { module = "software.amazon.awssdk:kms", version.ref = "aws-sdk" } +aws-profiles = { module = "software.amazon.awssdk:profiles", version.ref = "aws-sdk" } +aws-protocol-core = { module = "software.amazon.awssdk:protocol-core", version.ref = "aws-sdk" } +aws-regions = { module = "software.amazon.awssdk:regions", version.ref = "aws-sdk" } +aws-s3 = { module = "software.amazon.awssdk:s3", version.ref = "aws-sdk" } +aws-sdk-core = { module = "software.amazon.awssdk:sdk-core", version.ref = "aws-sdk" } +aws-sts = { module = "software.amazon.awssdk:sts", version.ref = "aws-sdk" } +aws-utils = { module = "software.amazon.awssdk:utils", version.ref = "aws-sdk" } +aws-metrics-spi = { module = "software.amazon.awssdk:metrics-spi", version.ref = "aws-sdk" } +aws-eventstream = { module = "software.amazon.eventstream:eventstream", version.ref = "eventstream" } + +# Netty (for AWS) +netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } +netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" } +netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } +netty-codec-http2 = { module = "io.netty:netty-codec-http2", version.ref = "netty" } +netty-common = { module = "io.netty:netty-common", version.ref = "netty" } +netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" } +netty-resolver = { module = "io.netty:netty-resolver", version.ref = "netty" } +netty-transport = { module = "io.netty:netty-transport", version.ref = "netty" } +netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" } +netty-transport-native-unix-common = { module = "io.netty:netty-transport-native-unix-common", version.ref = "netty" } +netty-nio-client = { module = "software.amazon.awssdk:netty-nio-client", version.ref = "netty-nio-client" } +netty-reactive-streams = { module = "com.typesafe.netty:netty-reactive-streams", version.ref = "netty-reactive" } +netty-reactive-streams-http = { module = "com.typesafe.netty:netty-reactive-streams-http", version.ref = "netty-reactive" } +reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactive-streams" } + +# GUI/UI +miglayout-core = { module = "com.miglayout:miglayout-core", version.ref = "miglayout" } +miglayout-swing = { module = "com.miglayout:miglayout-swing", version.ref = "miglayout" } +swingx-core = { module = "org.swinglabs:swingx-core", version.ref = "swingx-core" } +rsyntaxtextarea = { module = "com.fifesoft:rsyntaxtextarea", version.ref = "rsyntaxtextarea" } +autocomplete = { module = "com.fifesoft:autocomplete", version.ref = "autocomplete" } +looks = { module = "com.jgoodies:looks", version.ref = "looks" } +javaparser = { module = "com.google.code.javaparser:javaparser", version.ref = "javaparser" } +libphonenumber = { module = "com.googlecode.libphonenumber:libphonenumber", version.ref = "libphonenumber" } + +# Web/Stripes +displaytag = { module = "displaytag:displaytag", version.ref = "displaytag" } +json-simple = { module = "com.googlecode.json-simple:json-simple", version.ref = "json-simple" } + +# Testing +junit = { module = "junit:junit", version.ref = "junit" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } +hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } +byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } +byte-buddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy" } +objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" } + +# File handling +jcifs-ng = { module = "eu.agno3.jcifs:jcifs-ng", version.ref = "jcifs-ng" } +zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } + +# Local/Non-Maven-Central dependencies (from libs-local repository) +mirth-vocab = { module = "com.mirth:mirth-vocab", version.ref = "mirth-vocab" } +not-yet-commons-ssl = { module = "ca.juliusdavies:not-yet-commons-ssl", version.ref = "not-yet-commons-ssl" } +jai-imageio = { module = "com.sun.media:jai-imageio", version.ref = "jai-imageio" } +jai-imageio-client = { module = "com.sun.media:jai-imageio-client", version.ref = "jai-imageio" } +wsdl4j-fixed = { module = "wsdl4j:wsdl4j-fixed", version.ref = "wsdl4j-fixed" } +imagej-ij = { module = "imagej:ij", version.ref = "imagej" } +pdfrenderer = { module = "com.sun.pdfview:pdfrenderer", version.ref = "pdfrenderer" } +webdavclient4j-core = { module = "com.googlecode.webdavclient4j:webdavclient4j-core", version.ref = "webdavclient4j" } +wizard = { module = "com.mirth:wizard", version.ref = "wizard" } +language-support = { module = "com.mirth:language-support", version.ref = "language-support" } +openjfx-extensions = { module = "com.mirth:openjfx-extensions", version.ref = "openjfx-extensions" } +stripes = { module = "net.sourceforge.stripes:stripes", version.ref = "stripes" } + +[bundles] +# Common logging bundle +logging = ["log4j-api", "log4j-core", "log4j-bridge", "slf4j-api", "slf4j-log4j12"] + +# Commons bundle +commons = ["commons-lang3", "commons-io", "commons-collections4", "commons-text", "commons-logging", "commons-codec"] + +# Security bundle +security = ["bcprov-jdk18on", "bcpkix-jdk18on", "bcutil-jdk18on"] + +# Jackson bundle +jackson = ["jackson-annotations", "jackson-core", "jackson-databind"] + +# Database drivers +database-drivers = ["derby", "jtds", "sqlite-jdbc", "mysql-connector", "mssql-jdbc", "postgresql"] + +# Jersey core +jersey-core = ["jersey-client", "jersey-common", "jersey-server"] + +# Jetty bundle +jetty = ["jetty-server", "jetty-servlet", "jetty-webapp", "jetty-http", "jetty-io", "jetty-util"] + +# HAPI bundle +hapi = ["hapi-base", "hapi-structures-v21", "hapi-structures-v22", "hapi-structures-v23", "hapi-structures-v231", + "hapi-structures-v24", "hapi-structures-v25", "hapi-structures-v251", "hapi-structures-v26", + "hapi-structures-v27", "hapi-structures-v28", "hapi-structures-v281"] + +# Testing bundle +testing = ["junit", "mockito-core", "mockito-inline", "hamcrest", "byte-buddy", "byte-buddy-agent", "objenesis"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f8e1ee3125 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..1af9e0930b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..adff685a03 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..c4bdd3ab8e --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs-local/README.md b/libs-local/README.md new file mode 100644 index 0000000000..8ae0d2e7a7 --- /dev/null +++ b/libs-local/README.md @@ -0,0 +1,66 @@ +# Local Dependencies Repository + +This directory contains dependencies that are not available in Maven Central. +These JARs must be installed into this local Maven repository structure before building. + +## Installation + +Run the following script to install all local JARs into this repository: + +```bash +./install-local-deps.sh +``` + +## Required Local JARs + +The following JARs from the existing `lib/` directories need to be installed: + +### Server Extensions +| JAR File | Group ID | Artifact ID | Version | Source | +|----------|----------|-------------|---------|--------| +| `mirth-vocab.jar` | `com.mirth` | `mirth-vocab` | `1.0` | `server/lib/` | +| `jai_imageio.jar` | `com.sun.media` | `jai-imageio` | `1.1` | `server/lib/extensions/dimse/` | +| `wsdl4j-1.6.2-fixed.jar` | `wsdl4j` | `wsdl4j-fixed` | `1.6.2` | `server/lib/extensions/ws/` | +| `ij.jar` | `imagej` | `ij` | `1.53` | `server/lib/extensions/dicomviewer/` | +| `PDFRenderer.jar` | `com.sun.pdfview` | `pdfrenderer` | `0.9.1` | `server/lib/extensions/pdfviewer/` | +| `webdavclient4j-core-0.92.jar` | `com.googlecode.webdavclient4j` | `webdavclient4j-core` | `0.92` | `server/lib/extensions/file/` | + +### Client Libraries +| JAR File | Group ID | Artifact ID | Version | Source | +|----------|----------|-------------|---------|--------| +| `wizard.jar` | `com.mirth` | `wizard` | `1.0` | `client/lib/` | +| `language_support.jar` | `com.mirth` | `language-support` | `1.0` | `client/lib/` | +| `openjfx.jar` | `com.mirth` | `openjfx-extensions` | `1.0` | `client/lib/` | + +### Third-Party Non-Standard +| JAR File | Group ID | Artifact ID | Version | Source | +|----------|----------|-------------|---------|--------| +| `not-going-to-be-commons-ssl-0.3.18.jar` | `ca.juliusdavies` | `not-yet-commons-ssl` | `0.3.18` | `server/lib/` | +| `zip4j_1.3.3.jar` | `net.lingala.zip4j` | `zip4j` | `1.3.3` | `server/lib/` | + +### WebAdmin +| JAR File | Group ID | Artifact ID | Version | Source | +|----------|----------|-------------|---------|--------| +| `stripes.jar` | `net.sourceforge.stripes` | `stripes` | `1.6.0` | `webadmin/WebContent/WEB-INF/lib/` | + +## Directory Structure + +``` +libs-local/ +├── README.md # This file +├── install-local-deps.sh # Installation script +├── flat/ # Flat directory for direct JAR references +│ └── *.jar # JARs that can't be structured +└── com/ # Maven repository structure + └── mirth/ + └── mirth-vocab/ + └── 1.0/ + └── mirth-vocab-1.0.jar +``` + +## Adding New Local Dependencies + +1. Determine the groupId, artifactId, and version for the JAR +2. Add an install command to `install-local-deps.sh` +3. Update this README +4. Add the dependency to `gradle/libs.versions.toml` and the appropriate module's `build.gradle.kts` diff --git a/libs-local/install-local-deps.sh b/libs-local/install-local-deps.sh new file mode 100755 index 0000000000..7c3f2f3bec --- /dev/null +++ b/libs-local/install-local-deps.sh @@ -0,0 +1,159 @@ +#!/bin/bash +# Script to install non-Maven-Central JARs into local repository structure +# Run from the project root directory + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +LIBS_LOCAL="$SCRIPT_DIR" + +# Function to install a JAR into local Maven repository +install_jar() { + local src_jar="$1" + local group_id="$2" + local artifact_id="$3" + local version="$4" + + if [ ! -f "$src_jar" ]; then + echo "WARNING: Source JAR not found: $src_jar" + return 1 + fi + + # Create directory structure: groupId/artifactId/version/ + local group_path="${group_id//./\/}" + local target_dir="$LIBS_LOCAL/$group_path/$artifact_id/$version" + local target_jar="$target_dir/${artifact_id}-${version}.jar" + + mkdir -p "$target_dir" + + # Copy JAR + cp "$src_jar" "$target_jar" + + # Create minimal POM + cat > "$target_dir/${artifact_id}-${version}.pom" << EOF + + + 4.0.0 + $group_id + $artifact_id + $version + jar + +EOF + + echo "Installed: $group_id:$artifact_id:$version" +} + +echo "Installing local dependencies into $LIBS_LOCAL" +echo "================================================" + +# Server libraries +install_jar "$PROJECT_ROOT/server/lib/mirth-vocab.jar" \ + "com.mirth" "mirth-vocab" "1.0" + +install_jar "$PROJECT_ROOT/server/lib/not-going-to-be-commons-ssl-0.3.18.jar" \ + "ca.juliusdavies" "not-yet-commons-ssl" "0.3.18" + +install_jar "$PROJECT_ROOT/server/lib/zip4j_1.3.3.jar" \ + "net.lingala.zip4j" "zip4j" "1.3.3" + +install_jar "$PROJECT_ROOT/server/lib/backport-util-concurrent-Java60-3.1.jar" \ + "backport-util-concurrent" "backport-util-concurrent-java60" "3.1" + +# Server extension libraries - DICOM/DCM4CHE +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/jai_imageio.jar" \ + "com.sun.media" "jai-imageio" "1.1" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/dcm4che-core-2.0.29.jar" \ + "dcm4che" "dcm4che-core" "2.0.29" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/dcm4che-filecache-2.0.29.jar" \ + "dcm4che" "dcm4che-filecache" "2.0.29" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/dcm4che-net-2.0.29.jar" \ + "dcm4che" "dcm4che-net" "2.0.29" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/dcm4che-tool-dcmrcv-2.0.29.jar" \ + "dcm4che" "dcm4che-tool-dcmrcv" "2.0.29" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dimse/dcm4che-tool-dcmsnd-2.0.29.jar" \ + "dcm4che" "dcm4che-tool-dcmsnd" "2.0.29" + +install_jar "$PROJECT_ROOT/server/lib/extensions/ws/wsdl4j-1.6.2-fixed.jar" \ + "wsdl4j" "wsdl4j-fixed" "1.6.2" + +install_jar "$PROJECT_ROOT/server/lib/extensions/dicomviewer/ij.jar" \ + "imagej" "ij" "1.53" + +install_jar "$PROJECT_ROOT/server/lib/extensions/pdfviewer/PDFRenderer.jar" \ + "com.sun.pdfview" "pdfrenderer" "0.9.1" + +install_jar "$PROJECT_ROOT/server/lib/extensions/file/webdavclient4j-core-0.92.jar" \ + "com.googlecode.webdavclient4j" "webdavclient4j-core" "0.92" + +# Client libraries +install_jar "$PROJECT_ROOT/client/lib/wizard.jar" \ + "com.mirth" "wizard" "1.0" + +install_jar "$PROJECT_ROOT/client/lib/language_support.jar" \ + "com.mirth" "language-support" "1.0" + +install_jar "$PROJECT_ROOT/client/lib/openjfx.jar" \ + "com.mirth" "openjfx-extensions" "1.0" + +install_jar "$PROJECT_ROOT/client/lib/jai_imageio.jar" \ + "com.sun.media" "jai-imageio-client" "1.1" + +# Client GUI libraries with problematic Maven Central POMs +install_jar "$PROJECT_ROOT/client/lib/swingx-core-1.6.2.jar" \ + "org.swinglabs" "swingx-core" "1.6.2" + +install_jar "$PROJECT_ROOT/client/lib/looks-2.3.1.jar" \ + "com.jgoodies" "looks" "2.3.1" + +# javax/JAXB/JAXWS extension libraries +install_jar "$PROJECT_ROOT/server/lib/javax/jaxb/ext/istack-commons-runtime-3.0.6.jar" \ + "com.sun.istack" "istack-commons-runtime" "3.0.6" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/FastInfoset-1.2.13.jar" \ + "com.sun.xml.fastinfoset" "FastInfoset" "1.2.13" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/jsr181-api-1.0.jar" \ + "javax.jws" "jsr181-api" "1.0" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/management-api-3.2.1.b001.jar" \ + "org.glassfish.external" "management-api" "3.2.1.b001" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/gmbal-api-only-3.1.0.b001.jar" \ + "org.glassfish.gmbal" "gmbal-api-only" "3.1.0.b001" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/ha-api-3.1.9.jar" \ + "org.glassfish.ha" "ha-api" "3.1.9" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/policy-2.7.2.jar" \ + "com.sun.xml.ws" "policy" "2.7.2" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/mimepull-1.9.7.jar" \ + "org.jvnet.mimepull" "mimepull" "1.9.7" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/saaj-impl-1.0.jar" \ + "com.sun.xml.messaging.saaj" "saaj-impl" "1.0" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/stax-ex-1.8.jar" \ + "org.jvnet.staxex" "stax-ex" "1.8" + +install_jar "$PROJECT_ROOT/server/lib/javax/jaxws/ext/streambuffer-1.5.4.jar" \ + "com.sun.xml.stream.buffer" "streambuffer" "1.5.4" + +# WebAdmin libraries +install_jar "$PROJECT_ROOT/webadmin/WebContent/WEB-INF/lib/stripes.jar" \ + "net.sourceforge.stripes" "stripes" "1.6.0" + +echo "" +echo "================================================" +echo "Local dependencies installation complete!" +echo "" +echo "You can now build with: ./gradlew build" diff --git a/manager/.classpath b/manager/.classpath index 3a43a72677..79894c7a22 100644 --- a/manager/.classpath +++ b/manager/.classpath @@ -1,7 +1,13 @@ - - + + + + + + + + @@ -11,27 +17,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -39,17 +24,5 @@ - - - - - - - - - - - - - + diff --git a/manager/.project b/manager/.project index c6e58661fd..ddd7874637 100644 --- a/manager/.project +++ b/manager/.project @@ -1,6 +1,6 @@ - Manager + manager @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883978 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts new file mode 100644 index 0000000000..39a7092c24 --- /dev/null +++ b/manager/build.gradle.kts @@ -0,0 +1,82 @@ +plugins { + `java-library` + application +} + +description = "Mirth Connect Manager - Server management utility" + +// Manager uses traditional src/ layout +sourceSets { + main { + java { + srcDir("src") + } + resources { + srcDir("src") + include("**/*.png", "**/*.gif", "**/*.properties") + } + } +} + +dependencies { + // Project dependencies + api(project(":donkey")) + api(project(":server")) + + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + + // Apache Commons + api(libs.commons.beanutils) + api(libs.commons.codec) + api(libs.commons.collections4) + api(libs.commons.configuration2) + api(libs.commons.io) + api(libs.commons.lang3) + api(libs.commons.logging) + api(libs.commons.text) + + // HTTP Client + api(libs.httpclient) + api(libs.httpcore) + api(libs.httpmime) + + // JSON/XML + api(libs.xstream) + api(libs.xpp3) + + // UI Libraries + api(libs.miglayout.core) + api(libs.miglayout.swing) + api(libs.swingx.core) + api(libs.looks) + + // Utilities + api(libs.rhino) +} + +// Create mirth-manager-launcher.jar with manifest +val managerLauncherJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-manager-launcher") + from(sourceSets.main.get().output) + manifest { + attributes( + "Main-Class" to "com.mirth.connect.manager.ManagerLauncher", + "Class-Path" to "manager-lib/" + ) + } +} + +application { + mainClass.set("com.mirth.connect.manager.ManagerLauncher") +} + +tasks.named("assemble") { + dependsOn(managerLauncherJar) +} + +artifacts { + add("archives", managerLauncherJar) +} diff --git a/packaging/scripts/deb/postinst b/packaging/scripts/deb/postinst new file mode 100644 index 0000000000..9d35a046a9 --- /dev/null +++ b/packaging/scripts/deb/postinst @@ -0,0 +1,74 @@ +#!/bin/bash +# Debian post-install script for Open Integration Engine +# Sets up permissions, symlinks, and enables the service + +set -e + +OIE_USER="oie" +OIE_GROUP="oie" + +case "$1" in + configure) + # Set ownership of application directories + chown -R "$OIE_USER:$OIE_GROUP" /opt/oie + chown -R "$OIE_USER:$OIE_GROUP" /var/log/oie + chown -R "$OIE_USER:$OIE_GROUP" /var/lib/oie + chown -R "$OIE_USER:$OIE_GROUP" /etc/oie + + # Set proper permissions + chmod 755 /opt/oie + chmod 755 /var/log/oie + chmod 755 /var/lib/oie + chmod 755 /etc/oie + + # Make scripts executable + chmod +x /opt/oie/oieserver 2>/dev/null || true + + # Create symlinks for configuration (if conf directory exists and symlink doesn't) + if [ -d /opt/oie/conf ] && [ ! -L /etc/oie/conf ]; then + ln -sf /opt/oie/conf /etc/oie/conf + fi + + # Create symlink for logs (if not already linked) + if [ -d /opt/oie/logs ] && [ ! -L /var/log/oie/app ]; then + ln -sf /opt/oie/logs /var/log/oie/app + fi + + # Create appdata symlink to /var/lib/oie + if [ ! -d /opt/oie/appdata ]; then + mkdir -p /var/lib/oie/appdata + ln -sf /var/lib/oie/appdata /opt/oie/appdata + fi + + # Reload systemd to pick up the new service file + systemctl daemon-reload + + echo "" + echo "========================================" + echo "Open Integration Engine has been installed." + echo "" + echo "To start the service:" + echo " sudo systemctl start oie" + echo "" + echo "To enable automatic startup on boot:" + echo " sudo systemctl enable oie" + echo "" + echo "Configuration files are located at:" + echo " /opt/oie/conf (also linked from /etc/oie/conf)" + echo "" + echo "Logs are written to:" + echo " /opt/oie/logs (also linked from /var/log/oie/app)" + echo "========================================" + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + # Nothing to do + ;; + + *) + echo "postinst called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/deb/postrm b/packaging/scripts/deb/postrm new file mode 100644 index 0000000000..bc25a62ca1 --- /dev/null +++ b/packaging/scripts/deb/postrm @@ -0,0 +1,68 @@ +#!/bin/bash +# Debian post-remove script for Open Integration Engine +# Cleans up after removal + +set -e + +case "$1" in + remove) + # Reload systemd to remove the service + systemctl daemon-reload + + echo "" + echo "========================================" + echo "Open Integration Engine has been removed." + echo "" + echo "The following directories have been preserved:" + echo " /var/log/oie - Log files" + echo " /var/lib/oie - Application data" + echo " /etc/oie - Configuration files" + echo "" + echo "To completely remove all data, run:" + echo " sudo rm -rf /var/log/oie /var/lib/oie /etc/oie" + echo "" + echo "The 'oie' user and group have been preserved." + echo "To remove them, run:" + echo " sudo deluser oie" + echo " sudo delgroup oie" + echo "========================================" + ;; + + purge) + # Remove configuration and data on purge + systemctl daemon-reload + + echo "Purging Open Integration Engine data..." + + # Remove symlinks first to avoid following them during removal + rm -f /etc/oie/conf 2>/dev/null || true + rm -f /var/log/oie/app 2>/dev/null || true + rm -f /opt/oie/appdata 2>/dev/null || true + + # Remove directories + rm -rf /var/log/oie + rm -rf /var/lib/oie + rm -rf /etc/oie + + echo "Data purged." + + # Note: We don't remove the user/group automatically on purge + # as it may cause issues with file ownership elsewhere + echo "" + echo "The 'oie' user and group have been preserved." + echo "To remove them, run:" + echo " sudo deluser oie" + echo " sudo delgroup oie" + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # Nothing to do + ;; + + *) + echo "postrm called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/deb/preinst b/packaging/scripts/deb/preinst new file mode 100644 index 0000000000..4fe786beb2 --- /dev/null +++ b/packaging/scripts/deb/preinst @@ -0,0 +1,46 @@ +#!/bin/bash +# Debian pre-install script for Open Integration Engine +# Creates the oie user and group if they don't exist + +set -e + +OIE_USER="oie" +OIE_GROUP="oie" + +case "$1" in + install|upgrade) + # Create group if it doesn't exist + if ! getent group "$OIE_GROUP" > /dev/null 2>&1; then + addgroup --system "$OIE_GROUP" + echo "Created system group: $OIE_GROUP" + fi + + # Create user if it doesn't exist + if ! getent passwd "$OIE_USER" > /dev/null 2>&1; then + adduser --system \ + --ingroup "$OIE_GROUP" \ + --home /opt/oie \ + --no-create-home \ + --shell /usr/sbin/nologin \ + --gecos "Open Integration Engine service account" \ + "$OIE_USER" + echo "Created system user: $OIE_USER" + fi + + # Create required directories + mkdir -p /var/log/oie + mkdir -p /var/lib/oie + mkdir -p /etc/oie + ;; + + abort-upgrade) + # Nothing to do + ;; + + *) + echo "preinst called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/deb/prerm b/packaging/scripts/deb/prerm new file mode 100644 index 0000000000..172574272f --- /dev/null +++ b/packaging/scripts/deb/prerm @@ -0,0 +1,42 @@ +#!/bin/bash +# Debian pre-remove script for Open Integration Engine +# Stops the service before removal + +set -e + +case "$1" in + remove|purge) + echo "Stopping Open Integration Engine service..." + + # Stop the service if it's running + if systemctl is-active --quiet oie; then + systemctl stop oie + echo "Service stopped." + fi + + # Disable the service + if systemctl is-enabled --quiet oie 2>/dev/null; then + systemctl disable oie + echo "Service disabled." + fi + ;; + + upgrade|deconfigure) + # On upgrade, stop the service but don't disable it + if systemctl is-active --quiet oie; then + systemctl stop oie + echo "Service stopped for upgrade." + fi + ;; + + failed-upgrade) + # Nothing to do + ;; + + *) + echo "prerm called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/rpm/post-install.sh b/packaging/scripts/rpm/post-install.sh new file mode 100644 index 0000000000..45a70c6329 --- /dev/null +++ b/packaging/scripts/rpm/post-install.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# RPM post-install script for Open Integration Engine +# Sets up permissions, symlinks, and enables the service + +set -e + +OIE_USER="oie" +OIE_GROUP="oie" + +# Set ownership of application directories +chown -R "$OIE_USER:$OIE_GROUP" /opt/oie +chown -R "$OIE_USER:$OIE_GROUP" /var/log/oie +chown -R "$OIE_USER:$OIE_GROUP" /var/lib/oie +chown -R "$OIE_USER:$OIE_GROUP" /etc/oie + +# Set proper permissions +chmod 755 /opt/oie +chmod 755 /var/log/oie +chmod 755 /var/lib/oie +chmod 755 /etc/oie + +# Make scripts executable +chmod +x /opt/oie/oieserver 2>/dev/null || true + +# Create symlinks for configuration (if conf directory exists and symlink doesn't) +if [ -d /opt/oie/conf ] && [ ! -L /etc/oie/conf ]; then + ln -sf /opt/oie/conf /etc/oie/conf +fi + +# Create symlink for logs (if not already linked) +if [ -d /opt/oie/logs ] && [ ! -L /var/log/oie/app ]; then + ln -sf /opt/oie/logs /var/log/oie/app +fi + +# Create appdata symlink to /var/lib/oie +if [ ! -d /opt/oie/appdata ]; then + mkdir -p /var/lib/oie/appdata + ln -sf /var/lib/oie/appdata /opt/oie/appdata +fi + +# Reload systemd to pick up the new service file +systemctl daemon-reload + +# Enable service (but don't start automatically) +echo "" +echo "========================================" +echo "Open Integration Engine has been installed." +echo "" +echo "To start the service:" +echo " sudo systemctl start oie" +echo "" +echo "To enable automatic startup on boot:" +echo " sudo systemctl enable oie" +echo "" +echo "Configuration files are located at:" +echo " /opt/oie/conf (also linked from /etc/oie/conf)" +echo "" +echo "Logs are written to:" +echo " /opt/oie/logs (also linked from /var/log/oie/app)" +echo "========================================" + +exit 0 diff --git a/packaging/scripts/rpm/post-uninstall.sh b/packaging/scripts/rpm/post-uninstall.sh new file mode 100644 index 0000000000..c067734292 --- /dev/null +++ b/packaging/scripts/rpm/post-uninstall.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# RPM post-uninstall script for Open Integration Engine +# Cleans up after removal + +set -e + +# Only run on uninstall, not upgrade +# $1 will be 0 on uninstall, 1 on upgrade +if [ "$1" = "0" ]; then + # Reload systemd to remove the service + systemctl daemon-reload + + echo "" + echo "========================================" + echo "Open Integration Engine has been removed." + echo "" + echo "The following directories have been preserved:" + echo " /var/log/oie - Log files" + echo " /var/lib/oie - Application data" + echo " /etc/oie - Configuration files" + echo "" + echo "To completely remove all data, run:" + echo " sudo rm -rf /var/log/oie /var/lib/oie /etc/oie" + echo "" + echo "The 'oie' user and group have been preserved." + echo "To remove them, run:" + echo " sudo userdel oie" + echo " sudo groupdel oie" + echo "========================================" +fi + +exit 0 diff --git a/packaging/scripts/rpm/pre-install.sh b/packaging/scripts/rpm/pre-install.sh new file mode 100644 index 0000000000..d90eb0a156 --- /dev/null +++ b/packaging/scripts/rpm/pre-install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# RPM pre-install script for Open Integration Engine +# Creates the oie user and group if they don't exist + +set -e + +OIE_USER="oie" +OIE_GROUP="oie" + +# Create group if it doesn't exist +if ! getent group "$OIE_GROUP" > /dev/null 2>&1; then + groupadd --system "$OIE_GROUP" + echo "Created system group: $OIE_GROUP" +fi + +# Create user if it doesn't exist +if ! getent passwd "$OIE_USER" > /dev/null 2>&1; then + useradd --system \ + --gid "$OIE_GROUP" \ + --home-dir /opt/oie \ + --no-create-home \ + --shell /sbin/nologin \ + --comment "Open Integration Engine service account" \ + "$OIE_USER" + echo "Created system user: $OIE_USER" +fi + +# Create required directories +mkdir -p /var/log/oie +mkdir -p /var/lib/oie +mkdir -p /etc/oie + +exit 0 diff --git a/packaging/scripts/rpm/pre-uninstall.sh b/packaging/scripts/rpm/pre-uninstall.sh new file mode 100644 index 0000000000..dd19166924 --- /dev/null +++ b/packaging/scripts/rpm/pre-uninstall.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# RPM pre-uninstall script for Open Integration Engine +# Stops the service before removal + +set -e + +# Only run on uninstall, not upgrade +# $1 will be 0 on uninstall, 1 on upgrade +if [ "$1" = "0" ]; then + echo "Stopping Open Integration Engine service..." + + # Stop the service if it's running + if systemctl is-active --quiet oie; then + systemctl stop oie + echo "Service stopped." + fi + + # Disable the service + if systemctl is-enabled --quiet oie 2>/dev/null; then + systemctl disable oie + echo "Service disabled." + fi +fi + +exit 0 diff --git a/packaging/systemd/oie.service b/packaging/systemd/oie.service new file mode 100644 index 0000000000..45d5ced0a2 --- /dev/null +++ b/packaging/systemd/oie.service @@ -0,0 +1,47 @@ +[Unit] +Description=Open Integration Engine +Documentation=https://github.com/nextgenhealthcare/connect +After=network.target + +[Service] +Type=simple +User=oie +Group=oie +WorkingDirectory=/opt/oie + +# Environment configuration +EnvironmentFile=-/etc/oie/oie.env + +# JVM memory settings (override in /etc/oie/oie.env) +Environment="JAVA_OPTS=-Xms256m -Xmx1024m" + +# Start command +ExecStart=/opt/oie/oieserver start + +# Graceful shutdown +ExecStop=/opt/oie/oieserver stop +TimeoutStopSec=60 + +# Restart policy +Restart=on-failure +RestartSec=10 + +# Security hardening +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes + +# Allow write access to required directories +ReadWritePaths=/var/log/oie /var/lib/oie /opt/oie/appdata /opt/oie/logs + +# Limit resource usage +LimitNOFILE=65536 +LimitNPROC=4096 + +[Install] +WantedBy=multi-user.target diff --git a/packaging/systemd/oie.tmpfiles.conf b/packaging/systemd/oie.tmpfiles.conf new file mode 100644 index 0000000000..261be5328f --- /dev/null +++ b/packaging/systemd/oie.tmpfiles.conf @@ -0,0 +1,11 @@ +# systemd-tmpfiles configuration for Open Integration Engine +# This file ensures required directories exist with correct permissions + +# Log directory +d /var/log/oie 0755 oie oie - + +# Application data directory +d /var/lib/oie 0755 oie oie - + +# Runtime directory (for PID files, etc.) +d /run/oie 0755 oie oie - diff --git a/server/.classpath b/server/.classpath index 4b87617ad3..02a3b6de3e 100644 --- a/server/.classpath +++ b/server/.classpath @@ -1,180 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - - - - - - - - - - - - - - - - - - - + - + + + - + - + + - + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -183,91 +35,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/server/.project b/server/.project index d4d4f9ae99..22cf3d7424 100644 --- a/server/.project +++ b/server/.project @@ -1,6 +1,6 @@ - Server + server @@ -10,8 +10,25 @@ + + org.eclipse.buildship.core.gradleprojectbuilder + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883979 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 0000000000..7ca4cee7d8 --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,1410 @@ +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Properties +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.Future + +plugins { + `java-library` + id("com.netflix.nebula.ospackage") + distribution +} + +description = "Mirth Connect Server - Main server component" + +val mirthVersion: String by rootProject.extra + +// Server uses traditional src/ layout (not Maven-style) +sourceSets { + main { + java { + srcDir("src") + } + resources { + srcDirs("conf", "dbconf") + } + } + test { + java { + srcDir("test") + } + } +} + +dependencies { + // Project dependencies + api(project(":donkey")) + + // Apache Commons + api(libs.commons.beanutils) + api(libs.commons.cli) + api(libs.commons.codec) + api(libs.commons.collections) + api(libs.commons.collections4) + api(libs.commons.compress) + api(libs.commons.configuration2) + api(libs.commons.dbcp2) + api(libs.commons.dbutils) + api(libs.commons.digester3) + api(libs.commons.el) + api(libs.commons.email) + api(libs.commons.fileupload) + api(libs.commons.httpclient.legacy) + api(libs.commons.io) + api(libs.commons.jxpath) + api(libs.commons.lang3) + api(libs.commons.logging) + api(libs.commons.math3) + api(libs.commons.net) + api(libs.commons.pool2) + api(libs.commons.text) + api(libs.commons.vfs2) + + // HTTP Client + api(libs.httpclient) + api(libs.httpcore) + api(libs.httpmime) + + // Logging + api(libs.log4j.api) + api(libs.log4j.core) + api(libs.log4j.bridge) + api(libs.slf4j.api) + implementation(libs.slf4j.log4j12) + + // Security + api(libs.bcprov.jdk18on) + api(libs.bcpkix.jdk18on) + api(libs.bcutil.jdk18on) + api(libs.not.yet.commons.ssl) + + // JSON/XML + api(libs.jackson.annotations) + api(libs.jackson.core) + api(libs.jackson.databind) + api(libs.jackson.dataformat.cbor) + api(libs.jackson.dataformat.yaml) + api(libs.jackson.datatype.jsr310) + api(libs.xstream) + api(libs.xpp3) + api(libs.staxon) + api(libs.jdom2) + + // Database + api(libs.hikaricp) + api(libs.mybatis) + implementation(libs.derby) + implementation(libs.derbytools) + implementation(libs.jtds) + implementation(libs.sqlite.jdbc) + implementation(libs.mysql.connector) + implementation(libs.mssql.jdbc) + implementation(libs.postgresql) + implementation(libs.ojdbc8) + + // Jetty + api(libs.jetty.annotations) + api(libs.jetty.continuation) + api(libs.jetty.http) + api(libs.jetty.io) + api(libs.jetty.jndi) + api(libs.jetty.plus) + api(libs.jetty.rewrite) + api(libs.jetty.security) + api(libs.jetty.server) + api(libs.jetty.servlet) + api(libs.jetty.util) + api(libs.jetty.util.ajax) + api(libs.jetty.webapp) + api(libs.jetty.xml) + api(libs.jetty.schemas) + api(libs.apache.jsp) + api(libs.taglibs.standard.impl) + api(libs.taglibs.standard.spec) + api(libs.mortbay.apache.el) + api(libs.mortbay.apache.jsp) + api(libs.ecj) + + // Jersey/JAX-RS + api(libs.jersey.client) + api(libs.jersey.common) + api(libs.jersey.server) + api(libs.jersey.container.servlet) + api(libs.jersey.container.servlet.core) + api(libs.jersey.container.jetty.http) + api(libs.jersey.container.jetty.servlet) + api(libs.jersey.media.jaxb) + api(libs.jersey.media.multipart) + api(libs.jersey.proxy.client) + api(libs.jersey.guava) + api(libs.hk2.api) + api(libs.hk2.locator) + api(libs.hk2.utils) + api(libs.aopalliance.repackaged) + + // javax APIs + api(libs.javax.servlet.api) + api(libs.javax.inject) + api(libs.javax.json) + api(libs.javax.json.api) + api(libs.javax.mail) + api(libs.javax.ws.rs.api) + api(libs.javax.activation) + api(libs.javax.activation.api) + api(libs.javax.annotation.api) + api(libs.validation.api) + api(libs.persistence.api) + + // JAXB + api(libs.jaxb.api) + api(libs.jaxb.runtime) + api(libs.istack.commons.runtime) + api(libs.txw2) + + // JAX-WS + api(libs.jaxws.api) + api(libs.jaxws.rt) + api(libs.jaxws.tools) + api(libs.javax.xml.soap.api) + api(libs.fastinfoset) + api(libs.gmbal.api.only) + api(libs.ha.api) + api(libs.jsr181.api) + api(libs.management.api) + api(libs.mimepull) + api(libs.policy) + api(libs.saaj.impl) + api(libs.stax.ex) + api(libs.streambuffer) + + // Swagger + api(libs.swagger.annotations) + api(libs.swagger.core) + api(libs.swagger.jaxrs2) + api(libs.swagger.models) + api(libs.swagger.integration) + api(libs.swagger.jaxrs2.servlet.initializer) + api(libs.reflections) + api(libs.classgraph) + + // Google Guava + api(libs.guava) + implementation(libs.failureaccess) + implementation(libs.checker.qual) + implementation(libs.error.prone.annotations) + implementation(libs.j2objc.annotations) + implementation(libs.jsr305) + implementation(libs.listenablefuture) + + // Guice + api(libs.guice) + + // ASM + api(libs.asm) + api(libs.asm.analysis) + api(libs.asm.commons) + api(libs.asm.tree) + api(libs.asm.util) + + // Other utilities + api(libs.javassist) + api(libs.joda.time) + api(libs.java.semver) + api(libs.quartz) + api(libs.velocity.engine.core) + api(libs.velocity.tools.generic) + api(libs.rhino) + api(libs.jsch) + api(libs.jna) + api(libs.jna.platform) + api(libs.oshi.core) + api(libs.backport.util.concurrent) + api(libs.zip4j) + + // JMS + api(libs.geronimo.jms) + api(libs.geronimo.j2ee.management) + + // OSGi + api(libs.osgi.core) + api(libs.osgi.resource.locator) + + // HL7/HAPI + api(libs.hapi.base) + api(libs.hapi.structures.v21) + api(libs.hapi.structures.v22) + api(libs.hapi.structures.v23) + api(libs.hapi.structures.v231) + api(libs.hapi.structures.v24) + api(libs.hapi.structures.v25) + api(libs.hapi.structures.v251) + api(libs.hapi.structures.v26) + api(libs.hapi.structures.v27) + api(libs.hapi.structures.v28) + api(libs.hapi.structures.v281) + + // AWS SDK + api(libs.aws.annotations) + api(libs.aws.apache.client) + api(libs.aws.auth) + api(libs.aws.core) + api(libs.aws.json.protocol) + api(libs.aws.query.protocol) + api(libs.aws.xml.protocol) + api(libs.aws.http.client.spi) + api(libs.aws.kms) + api(libs.aws.profiles) + api(libs.aws.protocol.core) + api(libs.aws.regions) + api(libs.aws.s3) + api(libs.aws.sdk.core) + api(libs.aws.sts) + api(libs.aws.utils) + api(libs.aws.metrics.spi) + api(libs.aws.eventstream) + + // Netty (for AWS) + api(libs.netty.buffer) + api(libs.netty.codec) + api(libs.netty.codec.http) + api(libs.netty.codec.http2) + api(libs.netty.common) + api(libs.netty.handler) + api(libs.netty.resolver) + api(libs.netty.transport) + api(libs.netty.transport.native.epoll) + api(libs.netty.transport.native.unix.common) + api(libs.netty.nio.client) + api(libs.netty.reactive.streams) + api(libs.netty.reactive.streams.http) + api(libs.reactive.streams) + + // Extension-specific dependencies (conditionally included) + // DICOM + api(libs.dcm4che.core) + api(libs.dcm4che.filecache) + api(libs.dcm4che.net) + api(libs.dcm4che.tool.dcmrcv) + api(libs.dcm4che.tool.dcmsnd) + api(libs.jai.imageio) + + // Document processing + api(libs.flying.saucer.core) + api(libs.flying.saucer.pdf) { + exclude(group = "bouncycastle") + exclude(group = "org.bouncycastle", module = "bctsp-jdk14") + } + api(libs.itext) { + exclude(group = "bouncycastle") + exclude(group = "org.bouncycastle", module = "bctsp-jdk14") + } + api(libs.itext.rtf) { + exclude(group = "bouncycastle") + exclude(group = "org.bouncycastle", module = "bctsp-jdk14") + } + api(libs.openhtmltopdf.core) + api(libs.openhtmltopdf.pdfbox) + api(libs.pdfbox) + api(libs.fontbox) + api(libs.xmpbox) + api(libs.graphics2d) + + // File connectors + api(libs.jcifs.ng) + api(libs.webdavclient4j.core) + + // Web services + api(libs.wsdl4j.fixed) + + // Viewers + api(libs.imagej.ij) + api(libs.pdfrenderer) + + // Local dependencies + api(libs.mirth.vocab) + + // Test + testImplementation(libs.junit) + testImplementation(libs.mockito.core) + testImplementation(libs.mockito.inline) + testImplementation(libs.hamcrest) + testRuntimeOnly(libs.byte.buddy) + testRuntimeOnly(libs.byte.buddy.agent) + testRuntimeOnly(libs.objenesis) +} + +// Generate version.properties +val generateVersionProperties by tasks.registering { + val outputFile = file("$buildDir/resources/main/version.properties") + outputs.file(outputFile) + + doLast { + outputFile.parentFile.mkdirs() + val dateFormat = SimpleDateFormat("MMMM d, yyyy") + outputFile.writeText(""" + mirth.version=$mirthVersion + mirth.date=${dateFormat.format(Date())} + """.trimIndent()) + } +} + +tasks.named("processResources") { + dependsOn(generateVersionProperties) +} + +// ============================================================================= +// Core JAR Tasks +// ============================================================================= + +// Create mirth-crypto.jar +val cryptoJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-crypto") + from(sourceSets.main.get().output) { + include("com/mirth/commons/encryption/**") + } +} + +// Create mirth-client-core.jar +val clientCoreJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-client-core") + dependsOn(generateVersionProperties) + from(sourceSets.main.get().output) { + include("com/mirth/connect/client/core/**") + include("com/mirth/connect/model/**") + include("com/mirth/connect/userutil/**") + include("com/mirth/connect/util/**") + include("com/mirth/connect/server/util/ResourceUtil.class") + include("com/mirth/connect/server/util/DebuggerUtil.class") + include("org/mozilla/**") + include("org/glassfish/jersey/**") + include("de/**") + include("net/lingala/zip4j/unzip/**") + include("version.properties") + } +} + +// Create mirth-server.jar +val serverJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-server") + from(sourceSets.main.get().output) { + include("com/mirth/connect/server/**") + include("com/mirth/connect/model/**") + include("com/mirth/connect/util/**") + include("com/mirth/connect/plugins/**") + include("com/mirth/connect/connectors/**") + include("org/**") + include("net/sourceforge/jtds/ssl/**") + exclude("com/mirth/connect/server/launcher/**") + exclude("org/dcm4che2/**") + } + // Include JNLP file from project directory + from(projectDir) { + include("mirth-client.jnlp") + } +} + +// Configuration for launcher classpath dependencies +val launcherClasspath by configurations.creating { + extendsFrom(configurations.runtimeClasspath.get()) +} + +// Create mirth-server-launcher.jar with manifest +val serverLauncherJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-server-launcher") + from(sourceSets.main.get().output) { + include("com/mirth/connect/server/launcher/**") + include("com/mirth/connect/server/extprops/**") + } + // Dynamically build classpath from resolved dependencies with subdirectory paths + // These are the minimal dependencies needed by the launcher to start + val launcherDeps = listOf( + "commons-io", "commons-configuration2", "commons-lang3", + "commons-logging", "commons-beanutils", "commons-text", "commons-collections" + ) + doFirst { + // Build classpath entries with subdirectory paths + val classpathEntries = mutableListOf() + val manifestCopied = mutableSetOf() + + // Process launcher dependencies using categorization + serverLibCategories.entries.sortedByDescending { it.key.count { c -> c == '/' } }.forEach { (subdir, patterns) -> + configurations.runtimeClasspath.get().files + .filter { jar -> + jar.name !in manifestCopied && + launcherDeps.any { dep -> jar.name.startsWith(dep) } && + patterns.any { p -> jar.name.matches(Regex(p.replace("*", ".*"))) } + } + .forEach { jar -> + classpathEntries.add("server-lib/$subdir/${jar.name}") + manifestCopied.add(jar.name) + } + } + + // Add remaining launcher deps that didn't match any category (root level) + configurations.runtimeClasspath.get().files + .filter { jar -> + jar.name !in manifestCopied && + launcherDeps.any { dep -> jar.name.startsWith(dep) } + } + .forEach { jar -> + classpathEntries.add("server-lib/${jar.name}") + manifestCopied.add(jar.name) + } + + // Add log4j jars with subdirectory path + configurations.runtimeClasspath.get().files + .filter { it.name.startsWith("log4j-") } + .forEach { classpathEntries.add("server-lib/log4j/${it.name}") } + + manifest { + attributes( + "Main-Class" to "com.mirth.connect.server.launcher.MirthLauncher", + "Class-Path" to "${classpathEntries.sorted().joinToString(" ")} conf/" + ) + } + } +} + +// Create mirth-dbconf.jar +val dbconfJar by tasks.registering(Jar::class) { + archiveBaseName.set("mirth-dbconf") + from("dbconf") +} + +// Create userutil-sources.jar +val userutilSourcesJar by tasks.registering(Jar::class) { + archiveBaseName.set("userutil-sources") + from("src") { + include("com/mirth/connect/userutil/**.java") + include("com/mirth/connect/server/userutil/**.java") + exclude("**/package-info.java") + } +} + +// Create httpauth-userutil-sources.jar +val httpauthUserutilSourcesJar by tasks.registering(Jar::class) { + archiveBaseName.set("httpauth-userutil-sources") + destinationDirectory.set(file("$buildDir/userutil-sources")) + from("src") { + include("com/mirth/connect/plugins/httpauth/userutil/**.java") + exclude("**/package-info.java") + } +} + +// ============================================================================= +// Extension Definition +// ============================================================================= + +// Data class for extension configuration +data class ExtensionConfig( + val name: String, + val type: String, // "connector", "datatype", or "plugin" + val srcPackage: String, + val sharedClasses: List = emptyList(), + val serverClasses: List = emptyList(), + val hasLib: Boolean = true +) + +// Define all extensionConfigs +val extensionConfigs = listOf( + // Connectors + ExtensionConfig("dicom", "connector", "dimse", + sharedClasses = listOf("DICOMReceiverProperties", "DICOMDispatcherProperties")), + ExtensionConfig("doc", "connector", "doc", + sharedClasses = listOf("DocumentDispatcherProperties", "DocumentConnectorServletInterface", "PageSize", "Unit")), + ExtensionConfig("file", "connector", "file", + sharedClasses = listOf("SchemeProperties", "FTPSchemeProperties", "SmbDialectVersion", "SmbSchemeProperties", + "SftpSchemeProperties", "S3SchemeProperties", "FileReceiverProperties", "FileDispatcherProperties", + "FileScheme", "FileAction", "FileConnectorServletInterface")), + ExtensionConfig("http", "connector", "http", + sharedClasses = listOf("HttpReceiverProperties", "HttpDispatcherProperties", "HttpStaticResource", + "HttpStaticResource\$ResourceType", "HttpConnectorServletInterface")), + ExtensionConfig("jdbc", "connector", "jdbc", + sharedClasses = listOf("DatabaseReceiverProperties", "DatabaseDispatcherProperties", "DatabaseConnectionInfo", + "Table", "Column", "DatabaseConnectorServletInterface")), + ExtensionConfig("jms", "connector", "jms", + sharedClasses = listOf("JmsConnectorProperties", "JmsReceiverProperties", "JmsDispatcherProperties", + "JmsConnectorServletInterface")), + ExtensionConfig("js", "connector", "js", + sharedClasses = listOf("JavaScriptReceiverProperties", "JavaScriptDispatcherProperties")), + ExtensionConfig("smtp", "connector", "smtp", + sharedClasses = listOf("SmtpDispatcherProperties", "SmtpConnectorServletInterface", "Attachment")), + ExtensionConfig("tcp", "connector", "tcp", + sharedClasses = listOf("TcpReceiverProperties", "TcpDispatcherProperties", "TcpConnectorServletInterface")), + ExtensionConfig("vm", "connector", "vm", + sharedClasses = listOf("VmReceiverProperties", "VmDispatcherProperties")), + ExtensionConfig("ws", "connector", "ws", + sharedClasses = listOf("Binding", "WebServiceReceiverProperties", "WebServiceDispatcherProperties", + "DefinitionServiceMap", "DefinitionServiceMap\$DefinitionPortMap", "DefinitionServiceMap\$PortInformation", + "WebServiceConnectorServletInterface")), + + // Datatypes + ExtensionConfig("datatype-delimited", "datatype", "datatypes/delimited", + serverClasses = listOf("DelimitedDataTypeServerPlugin", "DelimitedBatchAdaptor", "DelimitedBatchReader")), + ExtensionConfig("datatype-dicom", "datatype", "datatypes/dicom", + serverClasses = listOf("DICOMDataTypeServerPlugin")), + ExtensionConfig("datatype-edi", "datatype", "datatypes/edi", + serverClasses = listOf("EDIDataTypeServerPlugin")), + ExtensionConfig("datatype-hl7v2", "datatype", "datatypes/hl7v2", + serverClasses = listOf("HL7v2DataTypeServerPlugin", "HL7v2BatchAdaptor")), + ExtensionConfig("datatype-hl7v3", "datatype", "datatypes/hl7v3", + serverClasses = listOf("HL7V3DataTypeServerPlugin")), + ExtensionConfig("datatype-ncpdp", "datatype", "datatypes/ncpdp", + serverClasses = listOf("NCPDPDataTypeServerPlugin")), + ExtensionConfig("datatype-xml", "datatype", "datatypes/xml", + serverClasses = listOf("XMLDataTypeServerPlugin")), + ExtensionConfig("datatype-raw", "datatype", "datatypes/raw", + serverClasses = listOf("RawDataTypeServerPlugin")), + ExtensionConfig("datatype-json", "datatype", "datatypes/json", + serverClasses = listOf("JSONDataTypeServerPlugin")), + + // Plugins + ExtensionConfig("directoryresource", "plugin", "directoryresource", + sharedClasses = listOf("DirectoryResourceProperties", "DirectoryResourceServletInterface")), + ExtensionConfig("dashboardstatus", "plugin", "dashboardstatus", + sharedClasses = listOf("ConnectionLogItem", "DashboardConnectorStatusServletInterface")), + ExtensionConfig("destinationsetfilter", "plugin", "destinationsetfilter", + sharedClasses = listOf("DestinationSetFilterStep", "DestinationSetFilterStep\$Behavior", + "DestinationSetFilterStep\$Condition")), + ExtensionConfig("dicomviewer", "plugin", "dicomviewer", hasLib = true), + ExtensionConfig("globalmapviewer", "plugin", "globalmapviewer", + sharedClasses = listOf("GlobalMapServletInterface")), + ExtensionConfig("httpauth", "plugin", "httpauth", + sharedClasses = listOf("HttpAuthConnectorPluginProperties", "HttpAuthConnectorPluginProperties\$AuthType", + "NoneHttpAuthProperties", "basic/BasicHttpAuthProperties", "digest/DigestHttpAuthProperties", + "digest/DigestHttpAuthProperties\$Algorithm", "digest/DigestHttpAuthProperties\$QOPMode", + "custom/CustomHttpAuthProperties", "javascript/JavaScriptHttpAuthProperties", + "oauth2/OAuth2HttpAuthProperties", "oauth2/OAuth2HttpAuthProperties\$TokenLocation")), + ExtensionConfig("imageviewer", "plugin", "imageviewer", hasLib = false), + ExtensionConfig("javascriptrule", "plugin", "javascriptrule", + sharedClasses = listOf("JavaScriptRule")), + ExtensionConfig("javascriptstep", "plugin", "javascriptstep", + sharedClasses = listOf("JavaScriptStep")), + ExtensionConfig("mapper", "plugin", "mapper", + sharedClasses = listOf("MapperStep", "MapperStep\$Scope")), + ExtensionConfig("messagebuilder", "plugin", "messagebuilder", + sharedClasses = listOf("MessageBuilderStep")), + ExtensionConfig("datapruner", "plugin", "datapruner", + sharedClasses = listOf("DataPrunerServletInterface")), + ExtensionConfig("mllpmode", "plugin", "mllpmode", + sharedClasses = listOf("MLLPModeProperties")), + ExtensionConfig("pdfviewer", "plugin", "pdfviewer", hasLib = true), + ExtensionConfig("textviewer", "plugin", "textviewer", hasLib = false), + ExtensionConfig("rulebuilder", "plugin", "rulebuilder", + sharedClasses = listOf("RuleBuilderRule", "RuleBuilderRule\$Condition")), + ExtensionConfig("serverlog", "plugin", "serverlog", + sharedClasses = listOf("ServerLogItem", "ServerLogServletInterface")), + ExtensionConfig("scriptfilerule", "plugin", "scriptfilerule", + sharedClasses = listOf("ExternalScriptRule")), + ExtensionConfig("scriptfilestep", "plugin", "scriptfilestep", + sharedClasses = listOf("ExternalScriptStep")), + ExtensionConfig("xsltstep", "plugin", "xsltstep", + sharedClasses = listOf("XsltStep")) +) + +// ============================================================================= +// JAR Categorization for server-lib Subdirectories +// ============================================================================= + +// JAR categorization for server-lib subdirectories to match official distribution structure +val serverLibCategories = mapOf( + "aws" to listOf("annotations-2.*", "apache-client-2.*", "auth-2.*", "aws-*", "eventstream-*", "http-client-spi-*", "kms-*", "metrics-spi-*", "profiles-*", "protocol-core-*", "regions-*", "s3-*", "sdk-core-*", "sts-*", "arns-*", "utils-2.*"), + "aws/ext/netty" to listOf("netty-*"), + "aws/ext" to listOf("reactive-streams-*"), + "commons" to listOf("commons-*", "httpclient-4.*", "httpcore-4.*", "httpmime-*"), + "database" to listOf("derby*", "jtds-*", "mssql-jdbc-*", "mysql-connector-*", "ojdbc*", "postgresql-*", "sqlite-jdbc-*", "ucp-*", "oraclepki-*", "osdt_*", "simplefan-*", "ons-*"), + "donkey" to listOf("HikariCP-*", "guice-*", "quartz-*", "slf4j-*", "javassist-*"), + "donkey/guava" to listOf("guava-*", "checker-qual-*", "error_prone_*", "failureaccess-*", "j2objc-*", "jsr305-*", "listenablefuture-*"), + "hapi" to listOf("hapi-*"), + "jackson" to listOf("jackson-*"), + "javax" to listOf("javax.activation-*", "javax.annotation-*", "javax.inject-*", "javax.json*", "javax.mail*", "javax.servlet-*", "javax.ws.rs-*", "jakarta.*"), + "javax/jaxb" to listOf("jaxb-api-*", "jaxb-runtime-*"), + "javax/jaxb/ext" to listOf("istack-commons-*", "txw2-*"), + "javax/jaxws" to listOf("jaxws-*", "javax.xml.soap-*"), + "javax/jaxws/ext" to listOf("FastInfoset-*", "gmbal-*", "ha-api-*", "jsr181-*", "management-api-*", "mimepull-*", "policy-*", "saaj-*", "stax-ex-*", "streambuffer-*"), + "jersey" to listOf("jersey-*"), + "jersey/ext" to listOf("aopalliance*", "asm-*", "hk2-*", "org.osgi.*", "osgi-resource-*", "persistence-api-*", "validation-api-*"), + "jetty" to listOf("jetty-*"), + "jetty/jsp" to listOf("apache-jsp-*", "apache-el-*", "taglibs-*", "ecj-*"), + "jms" to listOf("geronimo-*"), + "log4j" to listOf("log4j-*"), + "swagger" to listOf("swagger-*"), + "swagger/ext" to listOf("reflections-*") +) + +// ============================================================================= +// Setup Directory Assembly +// ============================================================================= + +val setupDir = file("$projectDir/setup") +val extBuildDir = file("$buildDir/extensionConfigs") + +// Collect extension shared JAR task names +val extensionSharedJarTasks = extensionConfigs + .filter { it.sharedClasses.isNotEmpty() || it.type == "datatype" } + .map { "${it.name}SharedJar" } + +// Main setup assembly task +val assembleSetup by tasks.registering { + group = "build" + description = "Assembles the complete setup directory" + + dependsOn( + "classes", + cryptoJar, + clientCoreJar, + serverJar, + serverLauncherJar, + dbconfJar, + userutilSourcesJar + ) + // Depend on extension shared JARs for client-lib + dependsOn(extensionSharedJarTasks) + + doLast { + // Create setup directory structure + setupDir.mkdirs() + file("$setupDir/conf").mkdirs() + file("$setupDir/extensionConfigs").mkdirs() + file("$setupDir/server-lib").mkdirs() + file("$setupDir/client-lib").mkdirs() + file("$setupDir/manager-lib").mkdirs() + file("$setupDir/cli-lib").mkdirs() + file("$setupDir/server-launcher-lib").mkdirs() + file("$setupDir/logs").mkdirs() + file("$setupDir/docs").mkdirs() + file("$setupDir/public_html").mkdirs() + file("$setupDir/public_api_html").mkdirs() + file("$setupDir/server-lib/donkey").mkdirs() + + // Copy donkey JARs to server-lib/donkey (without version numbers) + val donkeyProject = project(":donkey") + copy { + from(donkeyProject.tasks.named("donkeyModelJar")) + into("$setupDir/server-lib/donkey") + rename { "donkey-model.jar" } + } + copy { + from(donkeyProject.tasks.named("donkeyServerJar")) + into("$setupDir/server-lib/donkey") + rename { "donkey-server.jar" } + } + copy { + from(donkeyProject.tasks.named("donkeyDbconfJar")) + into("$setupDir/server-lib/donkey") + rename { "donkey-dbconf.jar" } + } + + // Copy server-lib dependencies with subdirectory organization + val copiedJars = mutableSetOf() + + // First pass: copy to categorized subdirectories (process deepest paths first) + serverLibCategories.entries.sortedByDescending { it.key.count { c -> c == '/' } }.forEach { (subdir, patterns) -> + val targetDir = file("$setupDir/server-lib/$subdir") + targetDir.mkdirs() + + configurations.runtimeClasspath.get().files + .filter { jar -> + jar.name !in copiedJars && + patterns.any { pattern -> + jar.name.matches(Regex(pattern.replace("*", ".*"))) + } + } + .forEach { jar -> + copy { + from(jar) + into(targetDir) + } + copiedJars.add(jar.name) + } + } + + // Second pass: copy remaining JARs to server-lib root + copy { + from(configurations.runtimeClasspath) + into("$setupDir/server-lib") + exclude { it.file.name in copiedJars } + exclude("**/ant/**") + } + + // Add duplicates at root level (matching official distribution) + // These JARs need to exist in both donkey/ and root for compatibility + val rootDuplicatePatterns = listOf("HikariCP-*", "guice-*", "quartz-*", "javassist-*") + configurations.runtimeClasspath.get().files + .filter { jar -> rootDuplicatePatterns.any { p -> jar.name.matches(Regex(p.replace("*", ".*"))) } } + .forEach { jar -> + copy { + from(jar) + into("$setupDir/server-lib") + } + } + + // Also copy log4j JARs to donkey/ (matching official distribution) + // log4j is in server-lib/log4j/ for the launcher, but also needs to be in donkey/ + configurations.runtimeClasspath.get().files + .filter { it.name.startsWith("log4j-") } + .forEach { jar -> + copy { + from(jar) + into("$setupDir/server-lib/donkey") + } + } + + // Copy core JARs to server-lib (without version numbers for launcher compatibility) + copy { + from(cryptoJar) + into("$setupDir/server-lib") + rename { "mirth-crypto.jar" } + } + copy { + from(clientCoreJar) + into("$setupDir/server-lib") + rename { "mirth-client-core.jar" } + } + copy { + from(serverJar) + into("$setupDir/server-lib") + rename { "mirth-server.jar" } + } + copy { + from(dbconfJar) + into("$setupDir/server-lib") + rename { "mirth-dbconf.jar" } + } + + // Copy launcher JAR to setup root (without version number for script compatibility) + copy { + from(serverLauncherJar) + into(setupDir) + rename { "mirth-server-launcher.jar" } + } + + // Copy userutil-sources to client-lib + copy { + from(userutilSourcesJar) + into("$setupDir/client-lib") + } + + // Copy mirth-client.jar from client project (without version number) + val clientProject = project(":client") + copy { + from(clientProject.tasks.named("clientJar")) + into("$setupDir/client-lib") + rename { "mirth-client.jar" } + } + + // Copy client lib dependencies + copy { + from("${clientProject.projectDir}/lib") { + exclude("*-shared.jar") + exclude("extensions/**") + } + into("$setupDir/client-lib") + } + + // Copy mirth-client-core.jar to client-lib (required by WebStartServlet) + copy { + from(clientCoreJar) + into("$setupDir/client-lib") + rename { "mirth-client-core.jar" } + } + + // Copy mirth-crypto.jar to client-lib (required by WebStartServlet) + copy { + from(cryptoJar) + into("$setupDir/client-lib") + rename { "mirth-crypto.jar" } + } + + // Copy mirth-vocab.jar to client-lib (required by WebStartServlet) + copy { + from("$projectDir/lib/mirth-vocab.jar") + into("$setupDir/client-lib") + } + + // Copy donkey-model.jar to client-lib (required by WebStartServlet) + copy { + from(donkeyProject.tasks.named("donkeyModelJar")) + into("$setupDir/client-lib") + rename { "donkey-model.jar" } + } + + // Copy extension shared JARs to client-lib (required for client deserialization) + extensionConfigs.filter { it.sharedClasses.isNotEmpty() || it.type == "datatype" }.forEach { ext -> + copy { + from("$extBuildDir/${ext.name}") { + include("*-shared-*.jar") + } + into("$setupDir/client-lib") + // Rename to remove version number: foo-shared-4.5.2.jar -> foo-shared.jar + rename { fileName -> + fileName.replace(Regex("-shared-[0-9.]+\\.jar$"), "-shared.jar") + } + } + } + + // Copy conf files + copy { + from("conf") + into("$setupDir/conf") + } + + // Copy public html files + copy { + from("public_html") + into("$setupDir/public_html") + exclude("Thumbs.db") + } + + // Copy public API html files + copy { + from("public_api_html") + into("$setupDir/public_api_html") + exclude("Thumbs.db") + } + + // Copy docs + copy { + from("docs") + into("$setupDir/docs") + } + + // Copy basedir includes + copy { + from("basedir-includes") + into(setupDir) + } + + // Make server script executable + file("$setupDir/oieserver").setExecutable(true) + } +} + +// ============================================================================= +// JAR Signing Configuration +// ============================================================================= + +val signingEnabled = project.findProperty("disableSigning")?.toString()?.toBoolean() != true + +fun loadKeystoreProperties(): Map { + val props = Properties() + val propsFile = file("keystore.properties") + if (propsFile.exists()) { + propsFile.inputStream().use { stream -> props.load(stream) } + } + val result = mutableMapOf() + props.forEach { key, value -> + result[key.toString()] = value.toString().replace("\${basedir}", projectDir.absolutePath) + } + return result +} + +val modifyJarManifests by tasks.registering { + group = "signing" + description = "Adds Web Start security attributes to JAR manifests" + + dependsOn(assembleSetup) + onlyIf { signingEnabled } + + doLast { + val clientLibDir = file("$setupDir/client-lib") + val extensionsDir = file("$setupDir/extensions") + val manifestFile = file("custom_manifest.mf") + + if (!manifestFile.exists()) { + logger.warn("custom_manifest.mf not found, skipping manifest modification") + return@doLast + } + + // Collect JARs from client-lib (skip BouncyCastle) + val clientLibJars = clientLibDir.listFiles()?.filter { + it.extension == "jar" && + !it.name.startsWith("bcp") && + !it.name.startsWith("bcutil") + } ?: emptyList() + + // Collect JARs from extensions (client, shared, and lib JARs - not server JARs) + val extensionJars = extensionsDir.walkTopDown() + .filter { it.extension == "jar" && !it.name.contains("-server") } + .toList() + + val jarsToModify = clientLibJars + extensionJars + + logger.lifecycle("Modifying manifests for ${jarsToModify.size} JARs (${clientLibJars.size} in client-lib, ${extensionJars.size} in extensions)") + + jarsToModify.parallelStream().forEach { jarFile -> + exec { + commandLine("jar", "umf", manifestFile.absolutePath, jarFile.absolutePath) + isIgnoreExitValue = true + } + } + } +} + +val signClientJars by tasks.registering { + group = "signing" + description = "Signs all client and extension JARs for Java Web Start" + + dependsOn(modifyJarManifests) + onlyIf { signingEnabled } + + doLast { + val keystoreProps = loadKeystoreProperties() + val keystore = keystoreProps["key.keystore"] ?: error("key.keystore not configured in keystore.properties") + val storepass = keystoreProps["key.storepass"] ?: error("key.storepass not configured in keystore.properties") + val alias = keystoreProps["key.alias"] ?: error("key.alias not configured in keystore.properties") + val keypass = keystoreProps["key.keypass"] ?: storepass + + val clientLibDir = file("$setupDir/client-lib") + val extensionsDir = file("$setupDir/extensions") + + // Collect JARs from client-lib + val clientLibJars = clientLibDir.listFiles()?.filter { it.extension == "jar" } ?: emptyList() + + // Collect JARs from extensions (client, shared, and lib JARs - not server JARs) + val extensionJars = extensionsDir.walkTopDown() + .filter { it.extension == "jar" && !it.name.contains("-server") } + .toList() + + val jarsToSign = clientLibJars + extensionJars + + logger.lifecycle("Signing ${jarsToSign.size} JARs with keystore: $keystore (${clientLibJars.size} in client-lib, ${extensionJars.size} in extensions)") + + val failedJars = ConcurrentHashMap() + + // Use a fixed thread pool with limited concurrency to avoid resource exhaustion + val executor = Executors.newFixedThreadPool(4) + val futures = mutableListOf>() + + for (jarFile in jarsToSign) { + futures.add(executor.submit { + var success = false + var lastError = "" + + repeat(5) { _ -> + if (!success) { + try { + val result = exec { + commandLine( + "jarsigner", + "-keystore", keystore, + "-storepass", storepass, + "-keypass", keypass, + "-digestalg", "SHA-256", + "-sigalg", "SHA256withRSA", + jarFile.absolutePath, + alias + ) + isIgnoreExitValue = true + } + if (result.exitValue == 0) { + success = true + } else { + lastError = "Exit code: ${result.exitValue}" + Thread.sleep(1000) + } + } catch (e: Exception) { + lastError = e.message ?: "Unknown error" + Thread.sleep(1000) + } + } + } + + if (!success) { + failedJars[jarFile.name] = lastError + } + }) + } + + // Wait for all signing tasks to complete + for (future in futures) { + future.get() + } + executor.shutdown() + + if (failedJars.isNotEmpty()) { + for ((name, error) in failedJars) { + logger.error("Failed to sign $name: $error") + } + throw GradleException("JAR signing failed for ${failedJars.size} files") + } + + logger.lifecycle("Successfully signed ${jarsToSign.size} JARs") + } +} + +// Note: Signing is wired via installExtensionClients -> signClientJars chain below + +// Create extension build tasks dynamically +extensionConfigs.forEach { ext -> + val baseName = ext.name + val srcBase = when (ext.type) { + "connector" -> "com/mirth/connect/connectors/${ext.srcPackage}" + "datatype" -> "com/mirth/connect/plugins/${ext.srcPackage}" + else -> "com/mirth/connect/plugins/${ext.srcPackage}" + } + + // Shared JAR task + if (ext.sharedClasses.isNotEmpty() || ext.type == "datatype") { + tasks.register("${baseName}SharedJar") { + archiveBaseName.set("$baseName-shared") + destinationDirectory.set(file("$extBuildDir/$baseName")) + + from(sourceSets.main.get().output) { + if (ext.sharedClasses.isNotEmpty()) { + ext.sharedClasses.forEach { className -> + include("$srcBase/$className.class") + } + } else if (ext.type == "datatype") { + // For datatypes, shared includes everything except server classes + include("$srcBase/**") + ext.serverClasses.forEach { className -> + exclude("$srcBase/$className.class") + } + } + } + } + } + + // Server JAR task - build for all extensions (connectors, datatypes, and plugins) + // Include all classes except the explicitly listed shared classes + tasks.register("${baseName}ServerJar") { + archiveBaseName.set("$baseName-server") + destinationDirectory.set(file("$extBuildDir/$baseName")) + + from(sourceSets.main.get().output) { + include("$srcBase/**") + ext.sharedClasses.forEach { className -> + exclude("$srcBase/$className.class") + } + } + } + + // Extension ZIP task + tasks.register("${baseName}ExtensionZip") { + archiveBaseName.set(baseName) + archiveVersion.set(mirthVersion) + destinationDirectory.set(file("$buildDir/dist/extensionConfigs")) + + // Explicit dependencies on JAR tasks + if (ext.sharedClasses.isNotEmpty() || ext.type == "datatype") { + dependsOn("${baseName}SharedJar") + } + dependsOn("${baseName}ServerJar") // Always include server JAR + + from("$extBuildDir/$baseName") + from("src/$srcBase") { + include("*.xml") + } + // Include lib dependencies if they exist (lib directories use srcPackage names) + val extLibDir = file("lib/extensions/${ext.srcPackage}") + if (extLibDir.exists()) { + from(extLibDir) { + into("lib") + } + } + } +} + +// Task to build all extensionConfigs +val buildExtensions by tasks.registering { + group = "build" + description = "Builds all extension JARs and ZIPs" + + extensionConfigs.forEach { ext -> + if (ext.sharedClasses.isNotEmpty() || ext.type == "datatype") { + dependsOn("${ext.name}SharedJar") + } + dependsOn("${ext.name}ServerJar") // Always build server JAR + dependsOn("${ext.name}ExtensionZip") + } +} + +// Task to install extensions to setup/extensions/ +val installExtensions by tasks.registering { + group = "build" + description = "Installs extensions to setup/extensions directory" + + dependsOn(buildExtensions) + dependsOn(httpauthUserutilSourcesJar) + + doLast { + val extensionsDir = file("$setupDir/extensions") + extensionsDir.mkdirs() + + extensionConfigs.forEach { ext -> + val extDir = file("$extensionsDir/${ext.name}") + extDir.mkdirs() + + val srcBase = when (ext.type) { + "connector" -> "com/mirth/connect/connectors/${ext.srcPackage}" + "datatype" -> "com/mirth/connect/plugins/${ext.srcPackage}" + else -> "com/mirth/connect/plugins/${ext.srcPackage}" + } + + // Copy plugin.xml + copy { + from("src/$srcBase") { + include("*.xml") + } + into(extDir) + } + + // Copy shared JAR (renamed to remove version) + if (ext.sharedClasses.isNotEmpty() || ext.type == "datatype") { + copy { + from("$extBuildDir/${ext.name}") { + include("*-shared-*.jar") + } + into(extDir) + rename { "${ext.name}-shared.jar" } + } + } + + // Copy server JAR (renamed to remove version) - always present + copy { + from("$extBuildDir/${ext.name}") { + include("*-server-*.jar") + } + into(extDir) + rename { "${ext.name}-server.jar" } + } + + // Copy lib dependencies if they exist (lib directories use srcPackage names) + val libSrcDir = file("lib/extensions/${ext.srcPackage}") + if (libSrcDir.exists() && libSrcDir.isDirectory()) { + copy { + from(libSrcDir) + into("$extDir/lib") + } + } + } + + // Copy httpauth userutil sources JAR + val httpauthExtDir = file("$extensionsDir/httpauth") + file("$httpauthExtDir/src").mkdirs() + copy { + from("$buildDir/userutil-sources") { + include("httpauth-userutil-sources*.jar") + } + into("$httpauthExtDir/src") + rename { "httpauth-userutil-sources.jar" } + } + + logger.lifecycle("Installed ${extensionConfigs.size} extensions to $extensionsDir") + } +} + +// Wire extension installation and signing to assembleSetup +// Order: assembleSetup -> installExtensions -> installExtensionClients -> signClientJars +assembleSetup.configure { + finalizedBy(installExtensions) +} + +// Install client extension JARs after server extensions are installed +installExtensions.configure { + finalizedBy(project(":client").tasks.named("installExtensionClients")) +} + +// Signing happens after all JARs are installed (including extension client JARs) +project(":client").tasks.named("installExtensionClients").configure { + finalizedBy(signClientJars) +} + +// modifyJarManifests needs extensions to be fully installed +modifyJarManifests.configure { + mustRunAfter(project(":client").tasks.named("installExtensionClients")) +} + +tasks.named("assemble") { + dependsOn(assembleSetup, buildExtensions) +} + +// Artifacts for other modules +artifacts { + add("archives", cryptoJar) + add("archives", clientCoreJar) + add("archives", serverJar) +} + +// ============================================================================= +// Linux Package Building (RPM, DEB, tar.gz) +// ============================================================================= + +val packagingDir = rootProject.file("packaging") + +// Common package configuration +val packageName = "oie" +val packageDescription = "Open Integration Engine - Healthcare integration platform" +val packageUrl = "https://github.com/nextgenhealthcare/connect" +val packageLicense = "MPL-2.0" +val packageVendor = "NextGen Healthcare" +val packageMaintainer = "OIE Development Team" + +// RPM Package Task +val oieRpm by tasks.registering(com.netflix.gradle.plugins.rpm.Rpm::class) { + dependsOn(assembleSetup) + + packageName = "oie" + release = "1" + version = mirthVersion + archStr = "x86_64" + os = org.redline_rpm.header.Os.LINUX + + summary = packageDescription + packageDescription = "Open Integration Engine is a cross-platform healthcare integration engine " + + "designed to facilitate interoperability between healthcare systems." + url = packageUrl + license = packageLicense + vendor = packageVendor + packager = packageMaintainer + packageGroup = "Applications/Healthcare" + + // Package dependencies + requires("java-17-openjdk-headless") + requires("systemd") + + // Pre/post install scripts + preInstall(file("${packagingDir}/scripts/rpm/pre-install.sh")) + postInstall(file("${packagingDir}/scripts/rpm/post-install.sh")) + preUninstall(file("${packagingDir}/scripts/rpm/pre-uninstall.sh")) + postUninstall(file("${packagingDir}/scripts/rpm/post-uninstall.sh")) + + // Application files -> /opt/oie/ + from("$projectDir/setup") { + into("/opt/oie") + user = "oie" + permissionGroup = "oie" + fileMode = 0x1A4 // 0644 + dirMode = 0x1ED // 0755 + } + + // Make oieserver script executable + from("$projectDir/setup") { + into("/opt/oie") + include("oieserver") + user = "oie" + permissionGroup = "oie" + fileMode = 0x1ED // 0755 + } + + // Systemd service file + from("${packagingDir}/systemd/oie.service") { + into("/usr/lib/systemd/system") + user = "root" + permissionGroup = "root" + fileMode = 0x1A4 // 0644 + } + + // Tmpfiles configuration + from("${packagingDir}/systemd/oie.tmpfiles.conf") { + into("/usr/lib/tmpfiles.d") + rename { "oie.conf" } + user = "root" + permissionGroup = "root" + fileMode = 0x1A4 // 0644 + } + + // Create empty directories + directory("/var/log/oie", 0x1ED) // 0755 + directory("/var/lib/oie", 0x1ED) // 0755 + directory("/etc/oie", 0x1ED) // 0755 +} + +// DEB Package Task +val oieDeb by tasks.registering(com.netflix.gradle.plugins.deb.Deb::class) { + dependsOn(assembleSetup) + + packageName = "oie" + release = "1" + version = mirthVersion + archStr = "amd64" + + summary = packageDescription + packageDescription = "Open Integration Engine is a cross-platform healthcare integration engine " + + "designed to facilitate interoperability between healthcare systems." + url = packageUrl + license = packageLicense + vendor = packageVendor + maintainer = packageMaintainer + packageGroup = "misc" // Debian section + + // Package dependencies + requires("default-jre-headless").or("openjdk-17-jre-headless") + requires("systemd") + + // Pre/post install scripts + preInstall(file("${packagingDir}/scripts/deb/preinst")) + postInstall(file("${packagingDir}/scripts/deb/postinst")) + preUninstall(file("${packagingDir}/scripts/deb/prerm")) + postUninstall(file("${packagingDir}/scripts/deb/postrm")) + + // Application files -> /opt/oie/ + from("$projectDir/setup") { + into("/opt/oie") + user = "oie" + permissionGroup = "oie" + fileMode = 0x1A4 // 0644 + dirMode = 0x1ED // 0755 + } + + // Make oieserver script executable + from("$projectDir/setup") { + into("/opt/oie") + include("oieserver") + user = "oie" + permissionGroup = "oie" + fileMode = 0x1ED // 0755 + } + + // Systemd service file + from("${packagingDir}/systemd/oie.service") { + into("/lib/systemd/system") + user = "root" + permissionGroup = "root" + fileMode = 0x1A4 // 0644 + } + + // Tmpfiles configuration + from("${packagingDir}/systemd/oie.tmpfiles.conf") { + into("/usr/lib/tmpfiles.d") + rename { "oie.conf" } + user = "root" + permissionGroup = "root" + fileMode = 0x1A4 // 0644 + } + + // Create empty directories + directory("/var/log/oie", 0x1ED) // 0755 + directory("/var/lib/oie", 0x1ED) // 0755 + directory("/etc/oie", 0x1ED) // 0755 +} + +// Distribution (tar.gz) configuration +distributions { + main { + distributionBaseName.set("oie") + contents { + from("$projectDir/setup") + } + } +} + +// Configure distTar to use gzip compression and depend on assembleSetup +tasks.named("distTar") { + dependsOn(assembleSetup) + compression = Compression.GZIP + archiveExtension.set("tar.gz") +} + +tasks.named("distZip") { + dependsOn(assembleSetup) +} + +// Combined task to build all Linux packages +val buildLinuxPackages by tasks.registering { + group = "distribution" + description = "Builds all Linux packages (RPM, DEB, tar.gz)" + dependsOn(oieRpm, oieDeb, "distTar") +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..37016e7b9c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,39 @@ +rootProject.name = "open-integration-engine" + +// Enable version catalog +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +// Include all modules +include("donkey") +include("server") +include("client") +include("command") +include("manager") +include("generator") +include("webadmin") + +// Configure plugin management +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +// Configure dependency resolution +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) + repositories { + mavenCentral() + // Local repository for non-Maven-Central JARs + maven { + name = "libs-local" + url = uri("${rootProject.projectDir}/libs-local") + } + // Fallback flat directory for any remaining local JARs + flatDir { + dirs("${rootProject.projectDir}/libs-local/flat") + } + } + // Version catalog is automatically loaded from gradle/libs.versions.toml +} diff --git a/webadmin/.classpath b/webadmin/.classpath index 79ab88054a..4de6f0cedf 100644 --- a/webadmin/.classpath +++ b/webadmin/.classpath @@ -1,15 +1,14 @@ - - + - + + - - - - + + + @@ -26,7 +25,5 @@ - - - + diff --git a/webadmin/.project b/webadmin/.project index e449fab99f..8dc9025db1 100644 --- a/webadmin/.project +++ b/webadmin/.project @@ -1,27 +1,32 @@ - WebAdmin + webadmin - org.eclipse.wst.jsdt.core.javascriptValidator + org.eclipse.jdt.core.javabuilder - org.eclipse.jdt.core.javabuilder + org.eclipse.wst.common.project.facet.core.builder - org.eclipse.wst.common.project.facet.core.builder + org.eclipse.wst.validation.validationbuilder - org.eclipse.wst.validation.validationbuilder + org.eclipse.buildship.core.gradleprojectbuilder + + + + + org.eclipse.wst.jsdt.core.javascriptValidator @@ -32,5 +37,17 @@ org.eclipse.wst.common.project.facet.core.nature org.eclipse.jdt.core.javanature org.eclipse.wst.jsdt.core.jsNature + org.eclipse.buildship.core.gradleprojectnature + + + 1768604883981 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/webadmin/build.gradle.kts b/webadmin/build.gradle.kts new file mode 100644 index 0000000000..7183edd7c1 --- /dev/null +++ b/webadmin/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + `java-library` + war +} + +description = "Mirth Connect WebAdmin - Web-based admin console" + +// WebAdmin uses traditional src/ layout with WebContent +sourceSets { + main { + java { + srcDir("src") + } + resources { + srcDir("src") + } + } +} + +dependencies { + // Project dependencies + api(project(":donkey")) + api(project(":server")) + + // Web dependencies (provided by container) + providedCompile(libs.javax.servlet.api) + providedCompile(libs.mortbay.apache.jsp) + + // Web frameworks + api(libs.stripes) + api(libs.displaytag) + api(libs.json.simple) + + // Logging + api(libs.commons.logging) +} + +// Configure WAR task +tasks.war { + archiveBaseName.set("webadmin") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + // Include WebContent directory (except web.xml which is handled by webXml property) + from("WebContent") { + into("") + exclude("WEB-INF/web.xml") + } + + // Set web.xml location + webXml = file("WebContent/WEB-INF/web.xml") + + // Include compiled classes + from(sourceSets.main.get().output) { + into("WEB-INF/classes") + } + + // Exclude libraries that should come from WEB-INF/lib in WebContent + rootSpec.exclude("WEB-INF/lib/*.jar") + + // Copy libs from WebContent + from("WebContent/WEB-INF/lib") { + into("WEB-INF/lib") + } +} + +// Task to copy WAR to setup directory +val copyWarToSetup by tasks.registering(Copy::class) { + dependsOn(tasks.war) + from(tasks.war) + into(file("${project(":server").projectDir}/setup/webapps")) +} + +tasks.named("assemble") { + dependsOn(tasks.war) +}