Skip to content

Commit

Permalink
Introduce parameter 'autoInstallMissingIgs' to automatically install …
Browse files Browse the repository at this point in the history
…IGs from the public registry

Fixes #306
  • Loading branch information
qligier committed Nov 11, 2024
1 parent 523feec commit 5c69cba
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 22 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Unreleased

- Introduce parameter 'autoInstallMissingIgs' to automatically install IGs from the public registry
[#306](https://github.com/ahdis/matchbox/issues/306)

2024/10/24 Release 3.9.6

- Remove lucene dependencies [#301](https://github.com/ahdis/matchbox/issues/301)
Expand Down
26 changes: 14 additions & 12 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,25 @@ matchbox:
# onlyOneEngine: true
# xVersion : false
# igsPreloaded: ch.fhir.ig.ch-core#4.0.0-ballot
# autoInstallMissingIgs: true
suppressWarnInfo:
hl7.fhir.r4.core#4.0.1:
#- "Constraint failed: dom-6:"
```

| Parameter | Card | Description |
|----------------------|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| implementationguides | 0..\* | the Implementation Guide and version which with which matchbox will be configured, you can provide by classpath, file, http address, if none is specified the FHIR package servers will be used (need to be online) |
| txServer | 0..1 | txServer to use, n/a if none (default), http://localhost:8080/fhir for internal (if server.port in application.yaml is 8080), http://tx.fhir.org for hl7 validator |
| txUseEcosystem | 0..1 | boolean true if none (default), if true asks tx servers for which CodeSystem they are authorative |
| txServerCache | 0..1 | boolean if respones are cached, true if none (default) |
| txLog | 0..1 | string indicating file location of log (end either in .txt or .html, default no logging |
| igsPreloaded | 0..\* | For each mentioned ImplementationGuide (comma separated) an engine will be created, which will be cached in memory as long the application is running. Other IG's will created on demand and will be cached for an hour for subsequent calls. Tradeoff between memory consumption and first response time (creating of engine might have duration of half a minute). Default no igs are preloaded. |
| onlyOneEngine | 0..1 | Implementation Guides can have multiple versions with different dependencies. Matchbox creates for transformation and validation an own engine for each Implementation Guide and its dependencies (default setting). You can switch this behavior, e.g. if you are using it in development and want to create and update resources or transform maps. Set the setting for onlyOneEngine to true. The changes are however not persisted and will be lost if matchbox is restarted. |
| httpReadOnly | 0..1 | Whether to allow creating, modifying or deleting resources on the server via the HTTP API or not. If `true`, IGs can only be loaded through the configuration. |
| suppressWarnInfo | 0..\* | A list of warning message to ignore while validating resources, per Implementation Guide and version. |
| extensions | 0..1 | Extensions not defined by the ImplementationgGuides which are accepted, comma separted list by url patterns, defaults to 'any' |
| Parameter | Card | Description |
|-----------------------|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| implementationguides | 0..\* | the Implementation Guide and version which with which matchbox will be configured, you can provide by classpath, file, http address, if none is specified the FHIR package servers will be used (need to be online) |
| txServer | 0..1 | txServer to use, n/a if none (default), http://localhost:8080/fhir for internal (if server.port in application.yaml is 8080), http://tx.fhir.org for hl7 validator |
| txUseEcosystem | 0..1 | boolean true if none (default), if true asks tx servers for which CodeSystem they are authorative |
| txServerCache | 0..1 | boolean if respones are cached, true if none (default) |
| txLog | 0..1 | string indicating file location of log (end either in .txt or .html, default no logging |
| igsPreloaded | 0..\* | For each mentioned ImplementationGuide (comma separated) an engine will be created, which will be cached in memory as long the application is running. Other IG's will created on demand and will be cached for an hour for subsequent calls. Tradeoff between memory consumption and first response time (creating of engine might have duration of half a minute). Default no igs are preloaded. |
| onlyOneEngine | 0..1 | Implementation Guides can have multiple versions with different dependencies. Matchbox creates for transformation and validation an own engine for each Implementation Guide and its dependencies (default setting). You can switch this behavior, e.g. if you are using it in development and want to create and update resources or transform maps. Set the setting for onlyOneEngine to true. The changes are however not persisted and will be lost if matchbox is restarted. |
| httpReadOnly | 0..1 | Whether to allow creating, modifying or deleting resources on the server via the HTTP API or not. If `true`, IGs can only be loaded through the configuration. |
| suppressWarnInfo | 0..\* | A list of warning message to ignore while validating resources, per Implementation Guide and version. |
| extensions | 0..1 | Extensions not defined by the ImplementationgGuides which are accepted, comma separted list by url patterns, defaults to 'any' |
| autoInstallMissingIgs | 0..1 | Whether to automatically install IGs from the public registry if they are not installed. Default to `false`. |

#### Suppress warning/information-level issues in validation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,10 @@ public PackageInstallationSpec getPackageInstallationSpec() {

public PackageInstallOutcomeJson load(ImplementationGuide theResource, PackageInstallOutcomeJson install) {
PackageInstallOutcomeJson installOutcome = packageInstallerSvc
.install(this.getPackageInstallationSpec().setName(theResource.getName())
.setPackageUrl(theResource.getUrl()).setVersion(theResource.getVersion()));
.install(this.getPackageInstallationSpec()
.setName(theResource.getName())
.setPackageUrl(theResource.getUrl())
.setVersion(theResource.getVersion()));
if (install != null) {
install.getMessage().addAll(installOutcome.getMessage());
return install;
Expand All @@ -188,10 +190,8 @@ public PackageInstallOutcomeJson load(ImplementationGuide theResource, PackageIn
public OperationOutcome load(ImplementationGuide theResource) {
PackageInstallOutcomeJson installOutcome = packageInstallerSvc.install(this.getPackageInstallationSpec()
.setPackageUrl(theResource.getUrl())
.addInstallResourceTypes(MatchboxPackageInstallerImpl.DEFAULT_INSTALL_TYPES.toArray(new String[0]))
.setName(theResource.getName())
.setVersion(theResource.getVersion())
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
.setVersion(theResource.getVersion()));

if (cliContext!=null && cliContext.getOnlyOneEngine()) {
MatchboxEngine engine = matchboxEngineSupport.getMatchboxEngine(FHIRVersion._4_0_1.getDisplay(), this.cliContext, false, false);
Expand Down Expand Up @@ -511,4 +511,25 @@ public ImplementationGuide read(HttpServletRequest theServletRequest, IIdType th
}
}

/**
* Returns whether the given ImplementationGuide is installed or not.
*/
@Override
public boolean has(final String packageId, final String packageVersion) {
return Boolean.TRUE.equals(new TransactionTemplate(this.myTxManager)
.execute(tx -> this.myPackageVersionDao.findByPackageIdAndVersion(packageId, packageVersion).isPresent()));
}

/**
* Installs the given ImplementationGuide from the internet registry.
*/
@Override
public void installFromInternetRegistry(final String packageId, final String packageVersion) {
this.packageInstallerSvc.install(
this.getPackageInstallationSpec()
.setName(packageId)
.setPackageUrl("https://packages2.fhir.org/packages/%s/%s".formatted(packageId, packageVersion))
.setVersion(packageVersion)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -515,4 +515,25 @@ public ImplementationGuide read(HttpServletRequest theServletRequest, IIdType th
}
}

/**
* Returns whether the given ImplementationGuide is installed or not.
*/
@Override
public boolean has(final String packageId, final String packageVersion) {
return Boolean.TRUE.equals(new TransactionTemplate(this.myTxManager)
.execute(tx -> this.myPackageVersionDao.findByPackageIdAndVersion(packageId, packageVersion).isPresent()));
}

/**
* Installs the given ImplementationGuide from the internet registry.
*/
@Override
public void installFromInternetRegistry(final String packageId, final String packageVersion) {
this.packageInstallerSvc.install(
this.getPackageInstallationSpec()
.setName(packageId)
.setPackageUrl("https://packages2.fhir.org/packages/%s/%s".formatted(packageId, packageVersion))
.setVersion(packageVersion)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -495,4 +495,25 @@ public ImplementationGuide read(HttpServletRequest theServletRequest, IIdType th
}
}

/**
* Returns whether the given ImplementationGuide is installed or not.
*/
@Override
public boolean has(final String packageId, final String packageVersion) {
return Boolean.TRUE.equals(new TransactionTemplate(this.myTxManager)
.execute(tx -> this.myPackageVersionDao.findByPackageIdAndVersion(packageId, packageVersion).isPresent()));
}

/**
* Installs the given ImplementationGuide from the internet registry.
*/
@Override
public void installFromInternetRegistry(final String packageId, final String packageVersion) {
this.packageInstallerSvc.install(
this.getPackageInstallationSpec()
.setName(packageId)
.setPackageUrl("https://packages2.fhir.org/packages/%s/%s".formatted(packageId, packageVersion))
.setVersion(packageVersion)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@
public interface MatchboxImplementationGuideProvider {

PackageInstallOutcomeJson loadAll(boolean replace);

/**
* Returns whether the given ImplementationGuide is installed or not.
*/
boolean has(final String packageId, final String packageVersion);

/**
* Installs the given ImplementationGuide from the internet registry.
*/
void installFromInternetRegistry(final String packageId, final String packageVersion);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.Duration;
import org.hl7.fhir.r5.model.OperationOutcome;
Expand Down Expand Up @@ -78,6 +77,9 @@ public class ValidationProvider {
@Autowired
private FhirContext myContext;

@Autowired
private MatchboxImplementationGuideProvider igProvider;

// @Operation(name = "$canonical", manualRequest = true, idempotent = true, returnParameters = {
// @OperationParam(name = "return", type = IBase.class, min = 1, max = 1) })
// public IBaseResource canonical(HttpServletRequest theRequest) {
Expand Down Expand Up @@ -132,6 +134,15 @@ public IBaseResource validate(final HttpServletRequest theRequest) {
}
}

// Check if the IG should be auto-installed
if (cliContext.isAutoInstallMissingIgs() && theRequest.getParameterMap().containsKey("ig")) {
final var parts = theRequest.getParameter("ig").split("#");
if (!this.igProvider.has(parts[0], parts[1])) {
log.debug("Auto-installing the IG '{}' version '{}'", parts[0], parts[1]);
this.igProvider.installFromInternetRegistry(parts[0], parts[1]);
}
}

if (theRequest.getParameter("extensions") != null) {
String extensions = theRequest.getParameter("extensions");
cliContext.setExtensions(new ArrayList<String>(Arrays.asList(extensions.split(","))));
Expand Down
12 changes: 12 additions & 0 deletions matchbox-server/src/main/java/ch/ahdis/matchbox/CliContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ public boolean isHttpReadOnly() {
return this.httpReadOnly;
}

private boolean autoInstallMissingIgs = false;

public boolean isAutoInstallMissingIgs() {
return this.autoInstallMissingIgs;
}

private boolean xVersion = false;

public boolean getXVersion() {
Expand Down Expand Up @@ -225,6 +231,7 @@ public CliContext(Environment environment) {
this.igsPreloaded = environment.getProperty("matchbox.fhir.context.igsPreloaded", String[].class);
this.onlyOneEngine = environment.getProperty("matchbox.fhir.context.onlyOneEngine", Boolean.class, false);
this.httpReadOnly = environment.getProperty("matchbox.fhir.context.httpReadOnly", Boolean.class, false);
this.autoInstallMissingIgs = environment.getProperty("matchbox.fhir.context.autoInstallMissingIgs", Boolean.class, false);
this.extensions = Arrays.asList(environment.getProperty("matchbox.fhir.context.extensions", String[].class, new String[]{"any"}));
this.xVersion = environment.getProperty("matchbox.fhir.context.xVersion", Boolean.class, false);
}
Expand All @@ -245,6 +252,7 @@ public CliContext(CliContext other) {
this.igsPreloaded = other.igsPreloaded;
this.onlyOneEngine = other.onlyOneEngine;
this.httpReadOnly = other.httpReadOnly;
this.autoInstallMissingIgs = other.autoInstallMissingIgs;
this.extensions = other.extensions;
this.xVersion = other.xVersion;
}
Expand Down Expand Up @@ -675,6 +683,7 @@ public boolean equals(final Object o) {
&& onlyOneEngine == that.onlyOneEngine
&& xVersion == that.xVersion
&& httpReadOnly == that.httpReadOnly
&& autoInstallMissingIgs == that.autoInstallMissingIgs
&& htmlInMarkdownCheck == that.htmlInMarkdownCheck
&& Objects.equals(extensions, that.extensions)
&& Objects.equals(txServer, that.txServer)
Expand Down Expand Up @@ -717,6 +726,7 @@ public int hashCode() {
showTerminologyRouting,
clearTxCache,
httpReadOnly,
autoInstallMissingIgs,
allowExampleUrls,
htmlInMarkdownCheck,
txServer,
Expand Down Expand Up @@ -782,6 +792,7 @@ public String toString() {
", onlyOneEngine=" + onlyOneEngine +
", xVersion=" + xVersion +
", httpReadOnly=" + httpReadOnly +
", autoInstallMissingIgs=" + autoInstallMissingIgs +
'}';
}

Expand Down Expand Up @@ -817,6 +828,7 @@ public void addContextToExtension(final Extension ext) {
addExtension(ext, "showTerminologyRouting", new BooleanType(this.showTerminologyRouting));
addExtension(ext, "clearTxCache", new BooleanType(this.clearTxCache));
addExtension(ext, "httpReadOnly", new BooleanType(this.httpReadOnly));
addExtension(ext, "autoInstallMissingIgs", new BooleanType(this.autoInstallMissingIgs));
addExtension(ext, "allowExampleUrls", new BooleanType(this.allowExampleUrls));
addExtension(ext, "txServer", new UriType(this.txServer));
addExtension(ext, "txServerCache", new BooleanType(this.txServerCache));
Expand Down
Loading

0 comments on commit 5c69cba

Please sign in to comment.