diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index bc04e30fda..1e8d79d5e9 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -8,9 +8,6 @@ on:
pull_request:
branches: [ master, webapi-3.0 ]
-env:
- DOCKER_IMAGE: ohdsi/webapi
-
jobs:
# Build and test the code
build:
@@ -23,7 +20,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
@@ -47,7 +44,7 @@ jobs:
run: mvn -B -P${{ env.MAVEN_PROFILE }} test
# Check that the docker image builds correctly
- # Push to ohdsi/atlas:master for commits on master.
+ # Push to ghcr.io for commits on master or webapi-3.0.
docker:
# The type of runner that the job will run on
runs-on: ubuntu-latest
@@ -55,7 +52,16 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
+
+ - name: Set Docker image name
+ run: |
+ REPO="${GITHUB_REPOSITORY:-ohdsi/webapi}"
+ DOCKER_IMAGE="ghcr.io/$(echo "${REPO}" | tr '[:upper:]' '[:lower:]')"
+ echo "DOCKER_IMAGE=${DOCKER_IMAGE}" >> $GITHUB_ENV
+
+ - name: Debug Docker image name
+ run: echo "DOCKER_IMAGE=${DOCKER_IMAGE}"
- name: Cache Docker layers
uses: actions/cache@v4
@@ -68,40 +74,54 @@ jobs:
# Add Docker labels and tags
- name: Docker meta
id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v1
+ uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}
+ tags: |
+ type=raw,value=dev,enable=${{ github.ref == 'refs/heads/master' }}
+ type=raw,value=3.0-dev,enable=${{ github.ref == 'refs/heads/webapi-3.0' }}
+ type=sha,prefix=pr-,enable=${{ github.event_name == 'pull_request' }}
+ type=ref,event=branch,prefix=branch-,enable=${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/webapi-3.0' }}
+
+ - name: Debug Docker metadata
+ run: |
+ echo "Docker metadata outputs:"
+ echo "version: ${{ steps.docker_meta.outputs.version }}"
+ echo "tags: ${{ steps.docker_meta.outputs.tags }}"
+ echo "labels: ${{ steps.docker_meta.outputs.labels }}"
+ echo "json: ${{ steps.docker_meta.outputs.json }}"
# Setup docker build environment
- name: Set up QEMU
- uses: docker/setup-qemu-action@v1
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
+ uses: docker/setup-buildx-action@v3
- name: Set build parameters
id: build_params
run: |
- echo "::set-output name=sha8::${GITHUB_SHA::8}"
- if [ "${{ github.event_name }}" != "pull_request" ] && [ "${{ github.ref }}" == "refs/heads/master" ]; then
- echo "::set-output name=push::true"
- echo "::set-output name=load::false"
- echo "::set-output name=platforms::linux/amd64,linux/arm64"
+ echo "sha8=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
+ if [ "${{ github.event_name }}" != "pull_request" ] && ( [ "${{ github.ref }}" == "refs/heads/master" ] || [ "${{ github.ref }}" == "refs/heads/webapi-3.0" ] ); then
+ echo "push=true" >> $GITHUB_OUTPUT
+ echo "load=false" >> $GITHUB_OUTPUT
+ echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
else
- echo "::set-output name=push::false"
- echo "::set-output name=load::true"
- echo "::set-output name=platforms::linux/amd64"
+ echo "push=false" >> $GITHUB_OUTPUT
+ echo "load=true" >> $GITHUB_OUTPUT
+ echo "platforms=linux/amd64" >> $GITHUB_OUTPUT
fi
- - name: Login to DockerHub
- uses: docker/login-action@v1
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
if: steps.build_params.outputs.push == 'true'
with:
- username: ${{ secrets.DOCKER_HUB_USERNAME }}
- password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: docker_build
- uses: docker/build-push-action@v2
+ uses: docker/build-push-action@v5
with:
context: ./
file: ./Dockerfile
@@ -113,6 +133,7 @@ jobs:
build-args: |
GIT_BRANCH=${{ steps.docker_meta.outputs.version }}
GIT_COMMIT_ID_ABBREV=${{ steps.build_params.outputs.sha8 }}
+ MAVEN_PROFILE=webapi-docker,tcache
tags: ${{ steps.docker_meta.outputs.tags }}
# Use runtime labels from docker_meta as well as fixed labels
labels: |
diff --git a/Dockerfile b/Dockerfile
index a899f7d542..e3d98cc282 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,8 @@
-FROM maven:3.9-eclipse-temurin-21 as builder
+FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /code
-ARG MAVEN_PROFILE=webapi-docker
+ARG MAVEN_PROFILE=webapi-docker,tcache
ARG MAVEN_PARAMS="" # can use maven options, e.g. -DskipTests=true -DskipUnitTests=true
ARG OPENTELEMETRY_JAVA_AGENT_VERSION=1.17.0
@@ -29,7 +29,7 @@ RUN mvn package ${MAVEN_PARAMS} \
# OHDSI WebAPI running as a Spring Boot executable JAR with Java 21
FROM index.docker.io/library/eclipse-temurin:21-jre
-MAINTAINER Lee Evans - www.ltscomputingllc.com
+LABEL maintainer="Lee Evans - www.ltscomputingllc.com"
# Any Java options to pass along, e.g. memory, garbage collection, etc.
ENV JAVA_OPTS=""
@@ -41,14 +41,20 @@ ENV DEFAULT_JAVA_OPTS="-Djava.security.egd=file:///dev/./urandom"
# set working directory to a fixed WebAPI directory
WORKDIR /var/lib/ohdsi/webapi
+RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/*
+
COPY --from=builder /code/opentelemetry-javaagent.jar .
COPY --from=builder /code/target/WebAPI.jar .
+RUN mkdir -p /tmp/trexsql && \
+ unzip -j WebAPI.jar 'BOOT-INF/lib/trexsql-ext-*.jar' -d /tmp && \
+ unzip -j /tmp/trexsql-ext-*.jar 'libtrexsql_java.so_linux_amd64' -d /tmp/trexsql 2>/dev/null || true && \
+ mv /tmp/trexsql/libtrexsql_java.so_linux_amd64 /tmp/trexsql/libtrexsql_java.so 2>/dev/null || true && \
+ rm -f /tmp/trexsql-ext-*.jar
+
EXPOSE 8080
USER 101
-# Run the executable JAR
-CMD exec java ${DEFAULT_JAVA_OPTS} ${JAVA_OPTS} \
- --add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED \
- -jar WebAPI.jar
+# Run the executable JAR with TrexSQL native library path
+CMD ["sh", "-c", "exec java ${DEFAULT_JAVA_OPTS} ${JAVA_OPTS} -Dorg.duckdb.lib_path=/tmp/trexsql/libtrexsql_java.so --add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED -jar WebAPI.jar"]
diff --git a/README.md b/README.md
index 84e6e2fd70..e617554ffa 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,26 @@ mvn clean package -DskipTests -Dpackaging.type=jar
java -jar target/WebAPI.jar --spring.profiles.active=webapi-postgresql
```
+## Database configuration (single source of truth)
+
+Set your datasource and schema once; the packaged properties reuse the shared schema key.
+
+Minimal local run example (PostgreSQL):
+
+```bash
+export WEBAPI_SCHEMA=webapi # optional; defaults to webapi
+export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/postgres
+export SPRING_DATASOURCE_USERNAME=postgres
+export SPRING_DATASOURCE_PASSWORD=your_password
+
+java -jar target/WebAPI.jar \
+ --spring.profiles.active=webapi-postgresql \
+ --datasource.ohdsi.schema=${WEBAPI_SCHEMA:-webapi}
+```
+
+Notes:
+- Batch uses a table prefix and the security datasource can be overridden if you choose a separate connection, but both are optional when you keep everything on the main datasource/schema.
+
## SAML Auth support
The following parameters are used:
diff --git a/pom.xml b/pom.xml
index 3bf51d8690..89e52605ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,6 @@
UTF-8
3.5.6
- 2.24.3
2.2.1
5.5.0
@@ -40,22 +39,21 @@
21
21
-
-
- com.microsoft.sqlserver.jdbc.SQLServerDriver
- jdbc:sqlserver://serverName;databaseName=databaseName
- user
- password
+
+ org.postgresql.Driver
+ jdbc:postgresql://localhost:5433/postgres?currentSchema=webapi
+ postgres
+ mypass
- sql server
- dbo
- sql server
+ postgresql
+ webapi
+ postgresql
- com.microsoft.sqlserver.jdbc.SQLServerDriver
- jdbc:sqlserver://serverName
- userWithWritePrivs
- password
- classpath:db/migration/sqlserver
+ org.postgresql.Driver
+ ${datasource.url}
+ ${datasource.username}
+ ${datasource.password}
+ classpath:db/migration/postgresql
${datasource.ohdsi.schema}
false
@@ -346,6 +344,16 @@
**/*.properties
+
+ application-test.properties
+
+
+
+ src/test/resources
+ false
+
+ application-test.properties
+
src/test/resources
@@ -402,8 +410,19 @@
@@ -435,8 +454,6 @@
--add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED
- ${git.branch}
- ${git.commit.id.abbrev}
${buildinfo.atlas.milestone.id}
${buildinfo.webapi.milestone.id}
${buildinfo.atlas.release.tag}
@@ -466,9 +483,6 @@
--add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED
--add-exports java.naming/com.sun.jndi.ldap=ALL-UNNAMED
-
- org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
-
@@ -483,6 +497,10 @@
-parameters
+
+
+ **/trexsql/**
+
@@ -491,9 +509,6 @@
3.5.2
${skipITtests}
-
- org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
-
@@ -566,6 +581,11 @@
+
+ central
+ Maven Central
+ https://repo.maven.apache.org/maven2
+
ohdsi
repo.ohdsi.org
@@ -591,26 +611,6 @@
-
- org.apache.logging.log4j
- log4j-api
- ${log4j2.version}
-
-
- org.apache.logging.log4j
- log4j-core
- ${log4j2.version}
-
-
- org.apache.logging.log4j
- log4j-web
- ${log4j2.version}
-
-
- org.apache.logging.log4j
- log4j-slf4j-impl
- ${log4j2.version}
-
org.springframework.boot
@@ -661,12 +661,6 @@
org.springframework.boot
spring-boot-starter
-
-
- org.springframework.boot
- spring-boot-starter-logging
-
-
@@ -691,7 +685,7 @@
org.springframework.boot
- spring-boot-starter-log4j2
+ spring-boot-starter-logging
org.springframework.boot
@@ -1126,7 +1120,7 @@
org.springframework.ldap
spring-ldap-core
- 2.3.2.RELEASE
+ 3.2.8
org.ohdsi
@@ -1256,11 +1250,6 @@
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
-
- com.github.p-hoffmann
- trexsql-ext
- v0.1.2
-
@@ -1269,8 +1258,23 @@
true
+
+
+ com.github.p-hoffmann
+ trexsql-ext
+ v0.1.18
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+
org.springframework.boot
spring-boot-maven-plugin
@@ -1936,7 +1940,7 @@
war
- war
+ jar
diff --git a/src/main/java/org/ohdsi/webapi/DataAccessConfig.java b/src/main/java/org/ohdsi/webapi/DataAccessConfig.java
index 075a85b086..fa585bf6b4 100644
--- a/src/main/java/org/ohdsi/webapi/DataAccessConfig.java
+++ b/src/main/java/org/ohdsi/webapi/DataAccessConfig.java
@@ -49,14 +49,21 @@ public class DataAccessConfig {
private Properties getJPAProperties() {
Properties properties = new Properties();
- properties.setProperty("hibernate.default_schema", this.env.getProperty("spring.jpa.properties.hibernate.default_schema"));
- properties.setProperty("hibernate.dialect", this.env.getProperty("spring.jpa.properties.hibernate.dialect"));
- properties.setProperty("hibernate.generate_statistics", this.env.getProperty("spring.jpa.properties.hibernate.generate_statistics"));
- properties.setProperty("hibernate.jdbc.batch_size", this.env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size"));
- properties.setProperty("hibernate.order_inserts", this.env.getProperty("spring.jpa.properties.hibernate.order_inserts"));
+ // Only set optional Hibernate properties when present to avoid null values
+ putIfPresent(properties, "hibernate.default_schema", this.env.getProperty("spring.jpa.properties.hibernate.default_schema"));
+ putIfPresent(properties, "hibernate.dialect", this.env.getProperty("spring.jpa.properties.hibernate.dialect"));
+ putIfPresent(properties, "hibernate.generate_statistics", this.env.getProperty("spring.jpa.properties.hibernate.generate_statistics"));
+ putIfPresent(properties, "hibernate.jdbc.batch_size", this.env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size"));
+ putIfPresent(properties, "hibernate.order_inserts", this.env.getProperty("spring.jpa.properties.hibernate.order_inserts"));
properties.setProperty("hibernate.id.new_generator_mappings", "true");
return properties;
}
+
+ private static void putIfPresent(Properties target, String key, String value) {
+ if (value != null) {
+ target.setProperty(key, value);
+ }
+ }
@Bean({"primaryDataSource", "dataSource"})
@DependsOn("defaultStringEncryptor")
diff --git a/src/main/java/org/ohdsi/webapi/cache/CacheService.java b/src/main/java/org/ohdsi/webapi/cache/CacheService.java
index e9fa67efd8..0c9e27cccd 100644
--- a/src/main/java/org/ohdsi/webapi/cache/CacheService.java
+++ b/src/main/java/org/ohdsi/webapi/cache/CacheService.java
@@ -58,7 +58,6 @@ public CacheService() {
@GET
- @Path("/")
@Produces(MediaType.APPLICATION_JSON)
public List getCacheInfoList() {
List caches = new ArrayList<>();
diff --git a/src/main/java/org/ohdsi/webapi/db/migartion/V2_6_0_20180807192421__cohortDetailsHashcodes.java b/src/main/java/org/ohdsi/webapi/db/migartion/V2_6_0_20180807192421__cohortDetailsHashcodes.java
new file mode 100644
index 0000000000..422daa9f14
--- /dev/null
+++ b/src/main/java/org/ohdsi/webapi/db/migartion/V2_6_0_20180807192421__cohortDetailsHashcodes.java
@@ -0,0 +1,29 @@
+package org.ohdsi.webapi.db.migartion;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.ohdsi.webapi.arachne.commons.config.flyway.ApplicationContextAwareSpringMigration;
+import java.util.List;
+import org.ohdsi.webapi.cohortdefinition.CohortDefinitionDetails;
+import org.ohdsi.webapi.cohortdefinition.CohortDefinitionDetailsRepository;
+
+/**
+ * Flyway Java migration to update hash codes for cohort definition details.
+ *
+ * Note: NOT a @Component - Flyway discovers this via classpath scanning.
+ * Dependencies are retrieved from ApplicationContext to avoid circular dependency issues.
+ */
+public class V2_6_0_20180807192421__cohortDetailsHashcodes extends ApplicationContextAwareSpringMigration {
+
+ @Override
+ public void migrate() throws JsonProcessingException {
+ // Get repository from Spring ApplicationContext (set by Flyway before migration runs)
+ CohortDefinitionDetailsRepository detailsRepository =
+ applicationContext.getBean(CohortDefinitionDetailsRepository.class);
+
+ final List allDetails = detailsRepository.findAll();
+ for (CohortDefinitionDetails details: allDetails) {
+ details.updateHashCode();
+ detailsRepository.save(details);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ohdsi/webapi/db/migartion/V2_8_0_20190410103000__migratePathwayResults.java b/src/main/java/org/ohdsi/webapi/db/migartion/V2_8_0_20190410103000__migratePathwayResults.java
new file mode 100644
index 0000000000..bc29310f52
--- /dev/null
+++ b/src/main/java/org/ohdsi/webapi/db/migartion/V2_8_0_20190410103000__migratePathwayResults.java
@@ -0,0 +1,181 @@
+package org.ohdsi.webapi.db.migartion;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.ohdsi.webapi.arachne.commons.config.flyway.ApplicationContextAwareSpringMigration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.json.JSONObject;
+import org.ohdsi.circe.helper.ResourceHelper;
+import org.ohdsi.sql.SqlRender;
+import org.ohdsi.sql.SqlSplit;
+import org.ohdsi.sql.SqlTranslate;
+import org.ohdsi.webapi.service.AbstractDaoService;
+import org.ohdsi.webapi.source.Source;
+import org.ohdsi.webapi.source.SourceDaimon;
+import org.ohdsi.webapi.source.SourceRepository;
+import org.ohdsi.webapi.util.CancelableJdbcTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.jdbc.core.PreparedStatementCreator;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import static org.ohdsi.webapi.Constants.Params.GENERATION_ID;
+
+/**
+ * Flyway Java migration to migrate pathway analysis results.
+ *
+ * Note: NOT a @Component - Flyway discovers this via classpath scanning.
+ * Dependencies are retrieved from ApplicationContext to avoid circular dependency issues.
+ */
+public class V2_8_0_20190410103000__migratePathwayResults extends ApplicationContextAwareSpringMigration {
+
+ private final static String SQL_PATH = "/db/migration/java/V2_8_0_20190410103000__migratePathwayResults/";
+ private static final Logger log = LoggerFactory.getLogger(V2_8_0_20190410103000__migratePathwayResults.class);
+
+ @Service
+ public static class MigrationDAO extends AbstractDaoService {
+
+ public void savePathwayCodes(List