-
Notifications
You must be signed in to change notification settings - Fork 178
add optional cache #2475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
add optional cache #2475
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
5055fcc
switch from war to jar
p-hoffmann 3aa82c8
add trex cache
p-hoffmann b67f8dd
various fixes
p-hoffmann 6b78a9b
cleanup
p-hoffmann bceb409
Merge branch 'webapi-3.0' into p-hoffmann/cache
p-hoffmann 4348a2a
cleanup
p-hoffmann 414a143
fix
p-hoffmann a097fa1
update trexsql
p-hoffmann ab1e87c
update cache implementation
p-hoffmann c0ed871
Merge branch 'webapi-3.0' into p-hoffmann/cache
p-hoffmann 87847c7
cleanup
p-hoffmann 78fabd9
cleanup p2
p-hoffmann 3f845de
cleanup p3
p-hoffmann 81e1ffb
fix
p-hoffmann f0208ed
fix
p-hoffmann 52d75a5
fix
p-hoffmann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package org.ohdsi.webapi.trexsql; | ||
|
|
||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Configuration properties for trexsql integration. | ||
| * Maps to trexsql.* in application properties. | ||
| */ | ||
| @Configuration | ||
| @ConfigurationProperties(prefix = "trexsql") | ||
| public class TrexSQLConfig { | ||
|
|
||
| private boolean enabled = false; | ||
| private String cachePath = "./data/cache"; | ||
| private String extensionsPath; | ||
| private Map<String, TrexSQLSourceConfig> sources = new HashMap<>(); | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| public Map<String, TrexSQLSourceConfig> getSources() { | ||
| return sources; | ||
| } | ||
|
|
||
| public void setSources(Map<String, TrexSQLSourceConfig> sources) { | ||
| this.sources = sources; | ||
| } | ||
|
|
||
| public TrexSQLSourceConfig getSourceConfig(String sourceKey) { | ||
| return sources.get(sourceKey); | ||
| } | ||
|
|
||
| public boolean isEnabledForSource(String sourceKey) { | ||
| if (!enabled) { | ||
| return false; | ||
| } | ||
| TrexSQLSourceConfig sourceConfig = sources.get(sourceKey); | ||
| return sourceConfig != null && sourceConfig.isEnabled(); | ||
| } | ||
| } |
105 changes: 105 additions & 0 deletions
105
src/main/java/org/ohdsi/webapi/trexsql/TrexSQLInstanceManager.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| 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; | ||
|
|
||
| /** | ||
| * Singleton manager for the TrexSQL instance. | ||
| * Provides lazy initialization and graceful shutdown. | ||
| */ | ||
| @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 Object trexsqlDb = null; | ||
| private final ReentrantLock initLock = new ReentrantLock(); | ||
|
|
||
| public TrexSQLInstanceManager(TrexSQLConfig config) { | ||
| this.config = config; | ||
| } | ||
|
|
||
| public Object getInstance() { | ||
| if (!config.isEnabled()) { | ||
| throw new IllegalStateException("TrexSQL is not enabled"); | ||
| } | ||
|
|
||
| if (trexsqlDb == null) { | ||
| initLock.lock(); | ||
| try { | ||
| if (trexsqlDb == null) { | ||
| log.info("Initializing TrexSQL instance"); | ||
| trexsqlDb = Trexsql.init(buildConfig()); | ||
| log.info("TrexSQL instance initialized successfully"); | ||
| } | ||
| } finally { | ||
| initLock.unlock(); | ||
| } | ||
| } | ||
| return trexsqlDb; | ||
| } | ||
|
|
||
| public boolean isAvailable() { | ||
| if (!config.isEnabled() || trexsqlDb == null) { | ||
| return false; | ||
| } | ||
| try { | ||
| return Trexsql.isRunning(trexsqlDb); | ||
| } catch (Exception e) { | ||
| log.warn("Error checking TrexSQL status: {}", e.getMessage()); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| public boolean isAttached(String databaseCode) { | ||
| if (trexsqlDb == null) { | ||
| return false; | ||
| } | ||
| try { | ||
| return Trexsql.isAttached(trexsqlDb, databaseCode); | ||
| } catch (Exception e) { | ||
| log.warn("Error checking if database {} is attached: {}", databaseCode, e.getMessage()); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| private Map<String, Object> buildConfig() { | ||
| Map<String, Object> initConfig = new HashMap<>(); | ||
|
|
||
| if (config.getExtensionsPath() != null && !config.getExtensionsPath().isEmpty()) { | ||
| initConfig.put("extensions-path", config.getExtensionsPath()); | ||
| } | ||
|
|
||
| return initConfig; | ||
| } | ||
|
|
||
| @PreDestroy | ||
| public void shutdown() { | ||
| initLock.lock(); | ||
| try { | ||
| if (trexsqlDb != null) { | ||
| log.info("Shutting down TrexSQL instance"); | ||
| try { | ||
| Trexsql.shutdown(trexsqlDb); | ||
| log.info("TrexSQL instance shut down successfully"); | ||
| } catch (Exception e) { | ||
| log.error("Error shutting down TrexSQL instance: {}", e.getMessage(), e); | ||
| } finally { | ||
| trexsqlDb = null; | ||
| } | ||
| } | ||
| } finally { | ||
| initLock.unlock(); | ||
| } | ||
| } | ||
| } |
122 changes: 122 additions & 0 deletions
122
src/main/java/org/ohdsi/webapi/trexsql/TrexSQLSearchProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| 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() | ||
| && trexsqlService.isEnabledForSource(vocabularyVersionKey) | ||
| && trexsqlService.isCacheAvailable(vocabularyVersionKey); | ||
| } | ||
|
|
||
| @Override | ||
| public int getPriority() { | ||
| return TREXSQL_PRIORITY; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<Concept> executeSearch(SearchProviderConfig searchConfig, String query, String rows) throws Exception { | ||
| String sourceKey = searchConfig.getSourceKey(); | ||
|
|
||
| if (!trexsqlService.isEnabledForSource(sourceKey)) { | ||
| log.debug("TrexSQL not enabled for source {}", sourceKey); | ||
| throw new IllegalStateException("TrexSQL not enabled for source: " + sourceKey); | ||
| } | ||
|
|
||
| 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<Map<String, Object>> results = trexsqlService.searchVocab(sourceKey, query, maxRows); | ||
| return mapToConcepts(results); | ||
| } catch (Exception e) { | ||
| log.error("TrexSQL search failed for source {}: {}", sourceKey, e.getMessage(), e); | ||
| throw new RuntimeException("TrexSQL search failed: " + e.getMessage(), e); | ||
p-hoffmann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| private int parseRows(String rows) { | ||
| if (rows == null || rows.isEmpty()) { | ||
| return 1000; | ||
| } | ||
| try { | ||
| return Integer.parseInt(rows); | ||
| } catch (NumberFormatException e) { | ||
| return 1000; | ||
| } | ||
| } | ||
|
|
||
| private Collection<Concept> mapToConcepts(List<Map<String, Object>> results) { | ||
| List<Concept> concepts = new ArrayList<>(); | ||
|
|
||
| for (Map<String, Object> row : results) { | ||
| Concept concept = new Concept(); | ||
|
|
||
| Object conceptId = row.get("concept_id"); | ||
| if (conceptId != null) { | ||
| concept.conceptId = ((Number) conceptId).longValue(); | ||
p-hoffmann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| concept.conceptName = (String) row.get("concept_name"); | ||
| concept.domainId = (String) row.get("domain_id"); | ||
| concept.vocabularyId = (String) row.get("vocabulary_id"); | ||
| concept.conceptClassId = (String) row.get("concept_class_id"); | ||
| concept.standardConcept = (String) row.get("standard_concept"); | ||
| concept.conceptCode = (String) row.get("concept_code"); | ||
| concept.invalidReason = (String) row.get("invalid_reason"); | ||
|
|
||
| Object validStartDate = row.get("valid_start_date"); | ||
| if (validStartDate instanceof java.sql.Date) { | ||
| concept.validStartDate = new java.util.Date(((java.sql.Date) validStartDate).getTime()); | ||
| } else if (validStartDate instanceof java.util.Date) { | ||
| concept.validStartDate = (java.util.Date) validStartDate; | ||
| } | ||
|
|
||
| Object validEndDate = row.get("valid_end_date"); | ||
| if (validEndDate instanceof java.sql.Date) { | ||
| concept.validEndDate = new java.util.Date(((java.sql.Date) validEndDate).getTime()); | ||
| } else if (validEndDate instanceof java.util.Date) { | ||
| concept.validEndDate = (java.util.Date) validEndDate; | ||
| } | ||
|
|
||
| concepts.add(concept); | ||
| } | ||
|
|
||
| return concepts; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.