summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java246
-rw-r--r--src/main/java/dev/dnpm/etl/processor/pseudonym/PseudonymRequestFailed.java12
2 files changed, 250 insertions, 8 deletions
diff --git a/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java b/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java
index 98f4ba6..8b93cdc 100644
--- a/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java
+++ b/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java
@@ -19,23 +19,253 @@
package dev.dnpm.etl.processor.pseudonym;
-import java.net.URI;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.parser.IParser;
+import dev.dnpm.etl.processor.config.GPasConfigProperties;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HashMap;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
+import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
+import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
+import org.apache.hc.core5.http.config.Registry;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.hl7.fhir.r4.model.Identifier;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
+import org.hl7.fhir.r4.model.StringType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryListener;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.backoff.ExponentialBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+@Component
+@ConditionalOnProperty(value = "app.pseudonymizer", havingValue = "GPAS")
public class GpasPseudonymGenerator implements Generator {
- private final URI uri;
+ private final String gPasUrl;
+ private final String psnTargetDomain;
+ private static FhirContext r4Context = FhirContext.forR4();
+ private final HttpHeaders httpHeader;
- private final String target;
+ private final RetryTemplate retryTemplate = defaultTemplate();
+
+ private final Logger log = LoggerFactory.getLogger(GpasPseudonymGenerator.class);
+
+ private SSLContext customSslContext;
+ private RestTemplate restTemplate;
+
+ @Autowired
+ public GpasPseudonymGenerator(GPasConfigProperties gpasCfg) {
+
+ this.gPasUrl = gpasCfg.getUri();
+ this.psnTargetDomain = gpasCfg.getTarget();
+ httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
+
+ try {
+ if (StringUtils.isNotBlank(gpasCfg.getSslCaLocation())) {
+ customSslContext = getSslContext(gpasCfg.getSslCaLocation());
+ }
+ } catch (IOException | KeyManagementException | KeyStoreException | CertificateException |
+ NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
- public GpasPseudonymGenerator(URI uri, String target) {
- this.uri = uri;
- this.target = target;
}
@Override
public String generate(String id) {
- // TODO Implement this
- return "?";
+ var gPasRequestBody = getGpasRequestBody(id);
+ var responseEntity = getGpasPseudonym(gPasRequestBody);
+ var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
+ .parseResource(responseEntity.getBody());
+
+ return unwrapPseudonym(gPasPseudonymResult);
}
+ @NotNull
+ public static String unwrapPseudonym(Parameters gPasPseudonymResult) {
+ Identifier pseudonym = (Identifier) gPasPseudonymResult.getParameter().stream().findFirst()
+ .get().getPart().stream().filter(a -> a.getName().equals("pseudonym")).findFirst()
+ .orElseGet(ParametersParameterComponent::new).getValue();
+
+ // pseudonym
+ return pseudonym.getSystem() + "|" + pseudonym.getValue();
+ }
+
+
+ @NotNull
+ protected ResponseEntity<String> getGpasPseudonym(String gPasRequestBody) {
+
+ HttpEntity<String> requestEntity = new HttpEntity<>(gPasRequestBody, this.httpHeader);
+ ResponseEntity<String> responseEntity;
+ var restTemplate = getRestTemplete();
+
+ try {
+ responseEntity = retryTemplate.execute(
+ ctx -> restTemplate.exchange(gPasUrl, HttpMethod.POST, requestEntity,
+ String.class));
+
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
+ log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
+ } else {
+ log.warn("API request unsuccessful. Response: {}", requestEntity.getBody());
+ throw new PseudonymRequestFailed("API request unsuccessful gPas unsuccessful.");
+ }
+
+ return responseEntity;
+ } catch (Exception unexpected) {
+ throw new PseudonymRequestFailed(
+ "API request due unexpected error unsuccessful gPas unsuccessful.", unexpected);
+ }
+ }
+
+ protected String getGpasRequestBody(String id) {
+ var requestParameters = new Parameters();
+ requestParameters.addParameter().setName("target")
+ .setValue(new StringType().setValue(psnTargetDomain));
+ requestParameters.addParameter().setName("original")
+ .setValue(new StringType().setValue(id));
+ final IParser iParser = r4Context.newJsonParser();
+ return iParser.encodeResourceToString(requestParameters);
+ }
+
+ @NotNull
+ protected HttpHeaders getHttpHeaders(String gPasUserName, String gPasPassword) {
+ var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ if (StringUtils.isBlank(gPasUserName) || StringUtils.isBlank(gPasPassword)) {
+ return headers;
+ }
+
+ String authHeader = gPasUserName + ":" + gPasPassword;
+ byte[] authHeaderBytes = authHeader.getBytes();
+ byte[] encodedAuthHeaderBytes = Base64.getEncoder().encode(authHeaderBytes);
+ String encodedAuthHeader = new String(encodedAuthHeaderBytes);
+
+ if (StringUtils.isNotBlank(gPasUserName) && StringUtils.isNotBlank(gPasPassword)) {
+ headers.set("Authorization", "Basic " + encodedAuthHeader);
+ }
+
+ return headers;
+ }
+
+ protected RetryTemplate defaultTemplate() {
+ RetryTemplate retryTemplate = new RetryTemplate();
+ ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
+ backOffPolicy.setInitialInterval(1000);
+ backOffPolicy.setMultiplier(1.25);
+ retryTemplate.setBackOffPolicy(backOffPolicy);
+ HashMap<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
+ retryableExceptions.put(RestClientException.class, true);
+ retryableExceptions.put(ConnectException.class, true);
+ RetryPolicy retryPolicy = new SimpleRetryPolicy(3, retryableExceptions);
+ retryTemplate.setRetryPolicy(retryPolicy);
+
+ retryTemplate.registerListener(new RetryListener() {
+ @Override
+ public <T, E extends Throwable> void onError(RetryContext context,
+ RetryCallback<T, E> callback, Throwable throwable) {
+ log.warn("HTTP Error occurred: {}. Retrying {}", throwable.getMessage(),
+ context.getRetryCount());
+ RetryListener.super.onError(context, callback, throwable);
+ }
+ });
+
+ return retryTemplate;
+ }
+
+ /**
+ * Read SSL root certificate and return SSLContext
+ *
+ * @param certificateLocation file location to root certificate (PEM)
+ * @return initialized SSLContext
+ * @throws IOException file cannot be read
+ * @throws CertificateException in case we have an invalid certificate of type X.509
+ * @throws KeyStoreException keystore cannot be initialized
+ * @throws NoSuchAlgorithmException missing trust manager algorithmus
+ * @throws KeyManagementException key management failed at init SSLContext
+ */
+ @Nullable
+ protected SSLContext getSslContext(String certificateLocation)
+ throws IOException, CertificateException, KeyStoreException, KeyManagementException, NoSuchAlgorithmException {
+
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+
+ FileInputStream fis = new FileInputStream(certificateLocation);
+ X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509")
+ .generateCertificate(new BufferedInputStream(fis));
+
+ ks.load(null, null);
+ ks.setCertificateEntry(Integer.toString(1), ca);
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, tmf.getTrustManagers(), null);
+
+ return sslContext;
+ }
+
+ protected RestTemplate getRestTemplete() {
+
+ if (restTemplate != null) {
+ return restTemplate;
+ }
+
+ if (customSslContext == null) {
+ restTemplate = new RestTemplate();
+ return restTemplate;
+ }
+ final var sslsf = new SSLConnectionSocketFactory(customSslContext);
+ final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
+ .register("https", sslsf).register("http", new PlainConnectionSocketFactory()).build();
+
+ final BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(
+ socketFactoryRegistry);
+ final CloseableHttpClient httpClient = HttpClients.custom()
+ .setConnectionManager(connectionManager).build();
+
+ final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
+ httpClient);
+ restTemplate = new RestTemplate(requestFactory);
+ return restTemplate;
+ }
}
diff --git a/src/main/java/dev/dnpm/etl/processor/pseudonym/PseudonymRequestFailed.java b/src/main/java/dev/dnpm/etl/processor/pseudonym/PseudonymRequestFailed.java
new file mode 100644
index 0000000..79b4ba6
--- /dev/null
+++ b/src/main/java/dev/dnpm/etl/processor/pseudonym/PseudonymRequestFailed.java
@@ -0,0 +1,12 @@
+package dev.dnpm.etl.processor.pseudonym;
+
+public class PseudonymRequestFailed extends RuntimeException {
+
+ public PseudonymRequestFailed(String message) {
+ super(message);
+ }
+
+ public PseudonymRequestFailed(String message, Throwable cause) {
+ super(message, cause);
+ }
+}