diff --git a/.gitignore b/.gitignore
index 925c44c0e8..f0784d4fbe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ specs/
docs
.claude
*.bak
+**/test-results/
logs
### Developer's personal properties ###
diff --git a/Dockerfile b/Dockerfile
index 092ffdb422..dd477393fc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,6 @@ FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /code
-ARG MAVEN_PROFILE=trexsql
ARG MAVEN_PARAMS="" # can use maven options, e.g. -DskipTests=true -DskipUnitTests=true
ARG OPENTELEMETRY_JAVA_AGENT_VERSION=1.17.0
@@ -17,8 +16,7 @@ COPY src /code/src
RUN mvn package ${MAVEN_PARAMS} \
-Dpackaging.type=jar \
-Dgit.branch=${GIT_BRANCH} \
- -Dgit.commit.id.abbrev=${GIT_COMMIT_ID_ABBREV} \
- -P${MAVEN_PROFILE}
+ -Dgit.commit.id.abbrev=${GIT_COMMIT_ID_ABBREV}
# OHDSI WebAPI running as a Spring Boot executable JAR with Java 21
FROM index.docker.io/library/eclipse-temurin:21-jre
@@ -40,11 +38,23 @@ 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 2>/dev/null && \
- unzip -j /tmp/trexsql-ext-*.jar 'libtrexsql_java.so_linux_amd64' -d /tmp/trexsql 2>/dev/null && \
- mv /tmp/trexsql/libtrexsql_java.so_linux_amd64 /tmp/trexsql/libtrexsql_java.so 2>/dev/null && \
- rm -f /tmp/trexsql-ext-*.jar || true)
+# Plugin setup: download trexsql plugin JAR
+ARG TREXSQL_VERSION=0.2.0
+RUN mkdir -p /opt/webapi/plugins && \
+ if curl -fL -o /opt/webapi/plugins/trexsql.jar \
+ "https://github.com/OHDSI/trex/releases/download/v${TREXSQL_VERSION}/trexsql-${TREXSQL_VERSION}.jar"; then \
+ echo "Downloaded trexsql plugin v${TREXSQL_VERSION}"; \
+ else \
+ echo "WARNING: Failed to download trexsql plugin v${TREXSQL_VERSION}, trexsql will be unavailable"; \
+ fi
+
+# Download native libtrexsql.so (JNA resolves it from linux-x86-64/ on the plugin classpath)
+ARG LIBTREXSQL_VERSION=v1.4.4-trex
+RUN mkdir -p /opt/webapi/plugins/linux-x86-64 && \
+ curl -fL -o /tmp/libtrexsql.zip \
+ "https://github.com/p-hoffmann/trexsql-rs/releases/download/${LIBTREXSQL_VERSION}/libtrexsql-linux-amd64.zip" && \
+ unzip -j /tmp/libtrexsql.zip 'libtrexsql.so' -d /opt/webapi/plugins/linux-x86-64/ && \
+ rm /tmp/libtrexsql.zip
# Create logs directory for logback before switching to non-root user
RUN mkdir -p logs && chown 101:101 logs
@@ -53,5 +63,4 @@ EXPOSE 8080
USER 101
-# 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"]
+CMD ["sh", "-c", "exec java ${DEFAULT_JAVA_OPTS} ${JAVA_OPTS} -Dloader.path=/opt/webapi/plugins --add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED -jar WebAPI.jar"]
diff --git a/docker/auth-test/run-tests.sh b/docker/auth-test/run-tests.sh
index 0feebf9e8e..ce2009cc09 100755
--- a/docker/auth-test/run-tests.sh
+++ b/docker/auth-test/run-tests.sh
@@ -60,7 +60,7 @@ fi
if [ "$BUILD_WEBAPI" = true ]; then
log_info "Building WebAPI..."
cd ../..
- mvn clean package -DskipTests -Dpackaging.type=jar -P webapi-postgresql,trexsql -B
+ mvn clean package -DskipTests -Dpackaging.type=jar -P webapi-postgresql -B
cd "$SCRIPT_DIR"
fi
diff --git a/docker/integration-test/run-tests.sh b/docker/integration-test/run-tests.sh
index bf25616409..766f42cf98 100755
--- a/docker/integration-test/run-tests.sh
+++ b/docker/integration-test/run-tests.sh
@@ -45,7 +45,7 @@ fi
if [ "$BUILD_WEBAPI" = true ]; then
log_info "Building WebAPI..."
cd ../..
- mvn clean package -DskipTests -Dpackaging.type=jar -P webapi-postgresql,trexsql -B
+ mvn clean package -DskipTests -Dpackaging.type=jar -P webapi-postgresql -B
cd "$SCRIPT_DIR"
fi
diff --git a/pom.xml b/pom.xml
index 2b24cbe599..41cc7a4381 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,6 +126,7 @@
spring-boot-maven-plugin
${spring.boot.version}
+ ZIP
false
false
org.ohdsi.webapi.WebApi
@@ -180,10 +181,6 @@
-parameters
-
-
- **/trexsql/**
-
@@ -775,38 +772,6 @@
-
- trexsql
-
- true
-
-
-
- com.github.p-hoffmann
- trexsql-ext
- v0.1.23
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
- true
-
-
-
-
-
webapi-oracle
diff --git a/src/main/java/org/ohdsi/webapi/plugins/PluginsConfigurationInfo.java b/src/main/java/org/ohdsi/webapi/plugins/PluginsConfigurationInfo.java
index e666410504..e25009bc42 100644
--- a/src/main/java/org/ohdsi/webapi/plugins/PluginsConfigurationInfo.java
+++ b/src/main/java/org/ohdsi/webapi/plugins/PluginsConfigurationInfo.java
@@ -4,11 +4,24 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collections;
+import java.util.List;
+
@Component
public class PluginsConfigurationInfo extends ConfigurationInfo {
private static final String KEY = "plugins";
- public PluginsConfigurationInfo(@Value("${atlasgis.enabled}") Boolean atlasgisEnabled) {
+ public PluginsConfigurationInfo(
+ @Autowired(required = false) List plugins,
+ @Value("${atlasgis.enabled}") Boolean atlasgisEnabled) {
+ if (plugins == null) {
+ plugins = Collections.emptyList();
+ }
+ for (WebApiPlugin plugin : plugins) {
+ properties.put(plugin.getId() + "Enabled", plugin.isActive());
+ }
properties.put("atlasgisEnabled", atlasgisEnabled);
}
diff --git a/src/main/java/org/ohdsi/webapi/plugins/WebApiPlugin.java b/src/main/java/org/ohdsi/webapi/plugins/WebApiPlugin.java
new file mode 100644
index 0000000000..8975eb3d3e
--- /dev/null
+++ b/src/main/java/org/ohdsi/webapi/plugins/WebApiPlugin.java
@@ -0,0 +1,8 @@
+package org.ohdsi.webapi.plugins;
+
+public interface WebApiPlugin {
+ String getId();
+ String getName();
+ String getVersion();
+ boolean isActive();
+}
diff --git a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLConfig.java b/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLConfig.java
deleted file mode 100644
index 2829256af5..0000000000
--- a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLConfig.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.ohdsi.webapi.trexsql;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * Global trexsql configuration. Per-source config is in the source table (is_cache_enabled).
- */
-@ConfigurationProperties(prefix = "trexsql")
-public class TrexSQLConfig {
-
- private boolean enabled = false;
- private String cachePath = "./data/cache";
- private String extensionsPath;
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- public String getCachePath() {
- return cachePath;
- }
-
- public void setCachePath(String cachePath) {
- this.cachePath = cachePath;
- }
-
- public String getExtensionsPath() {
- return extensionsPath;
- }
-
- public void setExtensionsPath(String extensionsPath) {
- this.extensionsPath = extensionsPath;
- }
-}
diff --git a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLInstanceManager.java b/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLInstanceManager.java
deleted file mode 100644
index e6112a8414..0000000000
--- a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLInstanceManager.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.ohdsi.webapi.trexsql;
-
-import org.trex.Trexsql;
-import jakarta.annotation.PreDestroy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.ReentrantLock;
-
-@Component
-@ConditionalOnProperty(name = "trexsql.enabled", havingValue = "true", matchIfMissing = false)
-public class TrexSQLInstanceManager {
-
- private static final Logger log = LoggerFactory.getLogger(TrexSQLInstanceManager.class);
-
- private final TrexSQLConfig config;
- private volatile boolean initialized = false;
- private volatile boolean initFailed = false;
- private final ReentrantLock initLock = new ReentrantLock();
-
- public TrexSQLInstanceManager(TrexSQLConfig config) {
- this.config = config;
- }
-
- public void ensureInitialized() {
- if (!config.isEnabled()) {
- throw new IllegalStateException("TrexSQL is not enabled");
- }
-
- if (initFailed) {
- return;
- }
-
- if (!initialized) {
- initLock.lock();
- try {
- if (!initialized && !initFailed) {
- log.info("Initializing TrexSQL instance");
- try {
- Trexsql.init(buildConfig());
- initialized = true;
- log.info("TrexSQL instance initialized successfully");
- } catch (Exception | Error e) {
- log.error("Failed to initialize TrexSQL: {}. TrexSQL features will be unavailable.", e.getMessage());
- initFailed = true;
- }
- }
- } finally {
- initLock.unlock();
- }
- }
- }
-
- public boolean isAvailable() {
- if (!config.isEnabled() || !initialized) {
- return false;
- }
- try {
- return Trexsql.isRunning();
- } catch (Exception e) {
- log.warn("Error checking TrexSQL status: {}", e.getMessage());
- return false;
- }
- }
-
- public boolean isAttached(String databaseCode) {
- if (!initialized) {
- return false;
- }
- try {
- return Trexsql.isAttached(databaseCode);
- } catch (Exception e) {
- log.warn("Error checking if database {} is attached: {}", databaseCode, e.getMessage());
- return false;
- }
- }
-
- private Map buildConfig() {
- Map initConfig = new HashMap<>();
-
- if (config.getExtensionsPath() != null && !config.getExtensionsPath().isEmpty()) {
- initConfig.put("extensions-path", config.getExtensionsPath());
- }
-
- if (config.getCachePath() != null && !config.getCachePath().isEmpty()) {
- initConfig.put("cache-path", config.getCachePath());
- }
-
- initConfig.put("allow-unsigned-extensions", true);
-
- return initConfig;
- }
-
- @PreDestroy
- public void shutdown() {
- initLock.lock();
- try {
- if (initialized) {
- log.info("Shutting down TrexSQL instance");
- try {
- Trexsql.shutdown();
- log.info("TrexSQL instance shut down successfully");
- } catch (Exception e) {
- log.error("Error shutting down TrexSQL instance: {}", e.getMessage(), e);
- } finally {
- initialized = false;
- }
- }
- } finally {
- initLock.unlock();
- }
- }
-}
diff --git a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLSearchProvider.java b/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLSearchProvider.java
deleted file mode 100644
index 3dd6e3eed8..0000000000
--- a/src/main/java/org/ohdsi/webapi/trexsql/TrexSQLSearchProvider.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.ohdsi.webapi.trexsql;
-
-import org.ohdsi.vocabulary.Concept;
-import org.ohdsi.vocabulary.SearchProvider;
-import org.ohdsi.vocabulary.SearchProviderConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * SearchProvider implementation using TrexSQL.
- */
-@Component
-@ConditionalOnProperty(name = "trexsql.enabled", havingValue = "true", matchIfMissing = false)
-public class TrexSQLSearchProvider implements SearchProvider {
-
- private static final Logger log = LoggerFactory.getLogger(TrexSQLSearchProvider.class);
-
- private static final int TREXSQL_PRIORITY = 1;
-
- private final TrexSQLService trexsqlService;
- private final TrexSQLConfig config;
-
- public TrexSQLSearchProvider(TrexSQLService trexsqlService, TrexSQLConfig config) {
- this.trexsqlService = trexsqlService;
- this.config = config;
- }
-
- @Override
- public boolean supports(String vocabularyVersionKey) {
- return config.isEnabled();
- }
-
- @Override
- public int getPriority() {
- return TREXSQL_PRIORITY;
- }
-
- @Override
- public Collection executeSearch(SearchProviderConfig searchConfig, String query, String rows) throws Exception {
- String sourceKey = searchConfig.getSourceKey();
-
- if (!trexsqlService.isCacheAvailable(sourceKey)) {
- log.debug("Cache not available for source {}", sourceKey);
- throw new IllegalStateException("TrexSQL cache not available for source: " + sourceKey);
- }
-
- int maxRows = parseRows(rows);
- log.debug("TrexSQL search for source {} with query: {}", sourceKey, query);
-
- try {
- List