Skip to content

Commit

Permalink
Added: Proxy in Fhir Related Converter
Browse files Browse the repository at this point in the history
  • Loading branch information
djuarezgf committed Oct 8, 2024
1 parent 83b3e51 commit cc86329
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- CQL to Bundle Converter
- Archive expired queries
- Blaze Page Size environment variable
- Proxy in Fhir Related Converter


### Changed
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/de/samply/converter/ConverterException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.samply.converter;

public class ConverterException extends Exception{
public ConverterException(Throwable cause) {
super(cause);
}
}
120 changes: 113 additions & 7 deletions src/main/java/de/samply/fhir/FhirRelatedConverter.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,140 @@
package de.samply.fhir;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import de.samply.converter.ConverterException;
import de.samply.converter.EmptySession;
import de.samply.converter.SourceConverterImpl;
import de.samply.exporter.ExporterConst;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.StringUtils;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import java.util.Set;

public abstract class FhirRelatedConverter<O> extends SourceConverterImpl<String, O, EmptySession> {

protected IGenericClient client;
protected final String sourceId;
private final String fhirStoreUrl;
private String proxyHost = null;
private Integer proxyPort = null;
private String proxyUser = null;
private String proxyPassword = null;
private final Set<String> noProxy;


public FhirRelatedConverter(String fhirStoreUrl, String sourceId) throws ConverterException {
this(fhirStoreUrl, sourceId, null, null);
}

public FhirRelatedConverter(String fhirStoreUrl, String sourceId) {
this.fhirStoreUrl = fhirStoreUrl;
public FhirRelatedConverter(String fhirStoreUrl,
String sourceId,
String httpProxy,
String noProxy) throws ConverterException {
this.sourceId = sourceId;
recreateFhirClient();
setProxyConfiguration(httpProxy);
this.noProxy = (StringUtils.hasText(noProxy)) ?
Set.of(noProxy.replace(" ", "").split(",")) : null;
this.client = recreateFhirClient(fhirStoreUrl);
}

private void setProxyConfiguration(String httpProxy) throws ConverterException {
if (StringUtils.hasText(httpProxy)) {
URL url = createUrl(httpProxy);
proxyHost = url.getHost();
proxyPort = url.getPort();
setProxyUserAndPassword(url.getUserInfo());
}
}

private URL createUrl(String httpProxy) throws ConverterException {
try {
return new URL(httpProxy);
} catch (MalformedURLException e) {
throw new ConverterException(e);
}
}

private void setProxyUserAndPassword(String userInfo) {
if (userInfo != null) {
String[] credentials = userInfo.split(":");
if (credentials.length == 2) {
proxyUser = credentials[0];
proxyPassword = credentials[1];
}
}
}

private IGenericClient recreateFhirClient(String blazeStoreUrl) {
FhirContext fhirContext = FhirContext.forR4();
createIRestfulClientFactoryWithProxy(fhirContext).ifPresent(fhirContext::setRestfulClientFactory);
return configureHttpClient(fhirContext).newRestfulGenericClient(blazeStoreUrl);
}

private Optional<IRestfulClientFactory> createIRestfulClientFactoryWithProxy(FhirContext fhirContext) {
if (StringUtils.hasText(proxyHost) && proxyPort != null) {
HttpHost httpHost = new HttpHost(proxyHost, proxyPort);
HttpClientBuilder httpClientBuilder = HttpClients.custom();
Optional<HttpRoutePlanner> httpRoutePlanner = createHttpRoutePlanner(httpHost);
httpRoutePlanner.ifPresentOrElse(httpClientBuilder::setRoutePlanner, () -> httpClientBuilder.setProxy(httpHost));
createCredentialsProvider().ifPresent(httpClientBuilder::setDefaultCredentialsProvider);
ApacheRestfulClientFactory clientFactory = new ApacheRestfulClientFactory(fhirContext);
clientFactory.setHttpClient(httpClientBuilder.build());
return Optional.of(clientFactory);
}
return Optional.empty();
}

// Note: Call it only if proxyHost and proxyPort are already set.
private Optional<CredentialsProvider> createCredentialsProvider() {
if (StringUtils.hasText(proxyUser) && StringUtils.hasText(proxyPassword)) {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
AuthScope authScope = new AuthScope(proxyHost, proxyPort);
Credentials credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword);
credentialsProvider.setCredentials(authScope, credentials);
return Optional.of(credentialsProvider);
}
return Optional.empty();
}

protected void recreateFhirClient() {
this.client = configureHttpClient(FhirContext.forR4()).newRestfulGenericClient(fhirStoreUrl);
private Optional<HttpRoutePlanner> createHttpRoutePlanner(@NotNull HttpHost proxy) {
if (noProxy != null && noProxy.size() > 0) {
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy) {
@Override
public HttpHost determineProxy(HttpHost target, org.apache.http.HttpRequest request,
org.apache.http.protocol.HttpContext context) throws HttpException {
// Check if the target host is in the "no proxy" set
if (noProxy.contains(target.getHostName())) {
return null; // No proxy for this host
}
return super.determineProxy(target, request, context);
}
};
return Optional.of(routePlanner);
}
return Optional.empty();
}

private FhirContext configureHttpClient(FhirContext fhirContext) {
IRestfulClientFactory restfulClientFactory = fhirContext.getRestfulClientFactory();
restfulClientFactory.setConnectTimeout(ExporterConst.DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * 1000);
restfulClientFactory.setConnectionRequestTimeout(ExporterConst.DEFAULT_CONNECTION_REQUEST_TIMEOUT_IN_SECONDS * 1000);
restfulClientFactory.setSocketTimeout(ExporterConst.DEFAULT_SOCKET_TIMEOUT_IN_SECONDS * 1000);
//TODO: Set Proxy
return fhirContext;
}

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/de/samply/fhir/FhirSearchQueryConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.gclient.IQuery;
import de.samply.converter.ConverterException;
import de.samply.converter.EmptySession;
import de.samply.converter.Format;
import de.samply.exporter.ExporterConst;
Expand All @@ -26,10 +27,14 @@ public class FhirSearchQueryConverter extends FhirRelatedConverter<Bundle> {
private final static Logger logger = BufferedLoggerFactory.getLogger(FhirSearchQueryConverter.class);
private int pageSize = ExporterConst.DEFAULT_FHIR_PAGE_SIZE;

public FhirSearchQueryConverter(String fhirStoreUrl, String sourceId) {
public FhirSearchQueryConverter(String fhirStoreUrl, String sourceId) throws ConverterException {
super(fhirStoreUrl, sourceId);
}

public FhirSearchQueryConverter(String fhirStoreUrl, String sourceId, String httpProxy, String noProxy) throws ConverterException {
super(fhirStoreUrl, sourceId, httpProxy, noProxy);
}


@Override
public Flux<Bundle> convert(String fhirSearchQuery, ConverterTemplate template, EmptySession session) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/de/samply/fhir/cql/CqlRelatedConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.MethodOutcome;
import de.samply.converter.ConverterException;
import de.samply.converter.EmptySession;
import de.samply.fhir.FhirRelatedConverter;
import de.samply.logger.BufferedLoggerFactory;
Expand All @@ -21,10 +22,14 @@ public abstract class CqlRelatedConverter<O> extends FhirRelatedConverter<O> {

protected abstract O convert(ConverterTemplate template, MeasureReport measureReport);

public CqlRelatedConverter(String fhirStoreUrl, String sourceId) {
public CqlRelatedConverter(String fhirStoreUrl, String sourceId) throws ConverterException {
super(fhirStoreUrl, sourceId);
}

public CqlRelatedConverter(String fhirStoreUrl, String sourceId, String httpProxy, String noProxy) throws ConverterException {
super(fhirStoreUrl, sourceId, httpProxy, noProxy);
}

@Override
protected Flux<O> convert(String cqlQuery, ConverterTemplate template, EmptySession session) {
return Flux.just(convert(template, fetchMeasureReport(cqlQuery, template)));
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/de/samply/fhir/cql/CqlToBundleConverter.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package de.samply.fhir.cql;

import de.samply.converter.ConverterException;
import de.samply.converter.Format;
import de.samply.template.ConverterTemplate;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.MeasureReport;

public class CqlToBundleConverter extends CqlRelatedConverter<Bundle> {

public CqlToBundleConverter(String fhirStoreUrl, String sourceId) {
public CqlToBundleConverter(String fhirStoreUrl, String sourceId) throws ConverterException {
super(fhirStoreUrl, sourceId);
}

public CqlToBundleConverter(String fhirStoreUrl, String sourceId, String httpProxy, String noProxy) throws ConverterException {
super(fhirStoreUrl, sourceId, httpProxy, noProxy);
}

@Override
public Format getInputFormat() {
return Format.CQL;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package de.samply.fhir.cql;

import de.samply.converter.ConverterException;
import de.samply.converter.Format;
import de.samply.exporter.ExporterConst;
import de.samply.template.ConverterTemplate;
import org.hl7.fhir.r4.model.MeasureReport;

public class CqlToFhirSearchConverter extends CqlRelatedConverter<String> {

public CqlToFhirSearchConverter(String fhirStoreUrl, String sourceId) {
public CqlToFhirSearchConverter(String fhirStoreUrl, String sourceId) throws ConverterException {
super(fhirStoreUrl, sourceId);
}

public CqlToFhirSearchConverter(String fhirStoreUrl, String sourceId, String httpProxy, String noProxy) throws ConverterException {
super(fhirStoreUrl, sourceId, httpProxy, noProxy);
}

@Override
public Format getInputFormat() {
return Format.CQL_DATA;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import de.samply.EnvironmentTestUtils;
import de.samply.container.Containers;
import de.samply.converter.ConverterException;
import de.samply.csv.ContainersToCsvConverter;
import de.samply.csv.Session;
import de.samply.exporter.ExporterConst;
Expand Down Expand Up @@ -37,7 +38,7 @@ class FhirSearchQueryToBundleConverterTest {
private TokenContext tokenContext = new TokenContext(null);

@BeforeEach
void setUp() {
void setUp() throws ConverterException {
this.fhirSearchQueryToBundleConverter = new FhirSearchQueryConverter(blazeStoreUrl, sourceId);
EnvironmentUtils environmentUtils = new EnvironmentUtils(
EnvironmentTestUtils.getEmptyMockEnvironment());
Expand Down

0 comments on commit cc86329

Please sign in to comment.