001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2026 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.test.app.clients; 011 012import ca.cdr.api.model.json.easyshare.CreateShlResponseJson; 013import ca.cdr.api.model.json.easyshare.ShlManifestRequestJson; 014import ca.cdr.api.model.json.easyshare.ShlManifestResponseJson; 015import ca.cdr.test.app.clients.common.HttpClientOwnership; 016import ca.cdr.test.app.clients.common.RequestFactoryUtil; 017import ca.cdr.test.app.clients.common.SmileTestHttpClient; 018import jakarta.annotation.Nonnull; 019import jakarta.annotation.Nullable; 020import org.apache.commons.lang3.Validate; 021import org.springframework.http.MediaType; 022import org.springframework.http.client.support.BasicAuthenticationInterceptor; 023import org.springframework.util.LinkedMultiValueMap; 024import org.springframework.util.MultiValueMap; 025import org.springframework.web.client.RestClient; 026 027import java.time.Instant; 028import java.util.Objects; 029 030public class SmartHealthLinkClient implements AutoCloseable { 031 032 private final RestClient myRestClient; 033 034 @Nullable 035 private final HttpClientOwnership myOwnership; 036 037 SmartHealthLinkClient(RestClient theRestClient) { 038 this(theRestClient, null); 039 } 040 041 private SmartHealthLinkClient(RestClient theRestClient, @Nullable HttpClientOwnership theOwnership) { 042 myRestClient = theRestClient; 043 myOwnership = theOwnership; 044 } 045 046 public static @Nonnull SmartHealthLinkClient open( 047 @Nonnull String theBaseUrl, @Nonnull String theUsername, @Nonnull String thePassword) { 048 return authenticatedClient(HttpClientOwnership.resolve(null), theBaseUrl, theUsername, thePassword); 049 } 050 051 public static @Nonnull SmartHealthLinkClient openAnonymous(@Nonnull String theBaseUrl) { 052 HttpClientOwnership ownership = HttpClientOwnership.resolve(null); 053 return new SmartHealthLinkClient(builderFor(theBaseUrl, ownership.client()).build(), ownership); 054 } 055 056 public static @Nonnull SmartHealthLinkClient issuingOn( 057 @Nonnull SmileTestHttpClient theHttpClient, 058 @Nonnull String theBaseUrl, 059 @Nonnull String theUsername, 060 @Nonnull String thePassword) { 061 return authenticatedClient( 062 HttpClientOwnership.resolve(theHttpClient), theBaseUrl, theUsername, thePassword); 063 } 064 065 private static @Nonnull SmartHealthLinkClient authenticatedClient( 066 @Nonnull HttpClientOwnership theOwnership, 067 @Nonnull String theBaseUrl, 068 @Nonnull String theUsername, 069 @Nonnull String thePassword) { 070 RestClient restClient = builderFor(theBaseUrl, theOwnership.client()) 071 .requestInterceptor(new BasicAuthenticationInterceptor(theUsername, thePassword)) 072 .build(); 073 return new SmartHealthLinkClient(restClient, theOwnership); 074 } 075 076 private static @Nonnull RestClient.Builder builderFor( 077 String theBaseUrl, @Nonnull SmileTestHttpClient theHttpClient) { 078 return RestClient.builder() 079 .baseUrl(theBaseUrl) 080 .requestFactory(RequestFactoryUtil.wrap(theHttpClient)) 081 .defaultRequest(r -> r.accept(MediaType.APPLICATION_JSON)); 082 } 083 084 @Nonnull 085 public CreateShlResponseJson createSmartHealthLink( 086 @Nonnull String theLabel, @Nonnull String theEntryContentType, @Nonnull String theEntryContent) { 087 return createSmartHealthLink(theLabel, theEntryContentType, theEntryContent, null, null, null); 088 } 089 090 @Nonnull 091 public CreateShlResponseJson createSmartHealthLink( 092 @Nonnull String theLabel, 093 @Nonnull String theEntryContentType, 094 @Nonnull String theEntryContent, 095 @Nullable String thePasscode, 096 @Nullable Instant theExpiry, 097 @Nullable String theSubjectFhirResourceId) { 098 Validate.notBlank(theLabel, "Label is required"); 099 Validate.notBlank(theEntryContentType, "Entry content type is required"); 100 Validate.notBlank(theEntryContent, "Entry content is required"); 101 102 MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); 103 form.add("label", theLabel); 104 form.add("entryContentType", theEntryContentType); 105 form.add("entryContentString", theEntryContent); 106 if (thePasscode != null) { 107 form.add("passcode", thePasscode); 108 } 109 if (theExpiry != null) { 110 form.add("expiryTime", theExpiry.toString()); 111 } 112 if (theSubjectFhirResourceId != null) { 113 form.add("subjectFhirResourceId", theSubjectFhirResourceId); 114 } 115 116 CreateShlResponseJson result = myRestClient 117 .post() 118 .uri("shl/manifest") 119 .contentType(MediaType.APPLICATION_FORM_URLENCODED) 120 .body(form) 121 .retrieve() 122 .body(CreateShlResponseJson.class); 123 return Objects.requireNonNull(result); 124 } 125 126 @Nonnull 127 public ShlManifestResponseJson fetchManifest( 128 @Nonnull String thePublicId, @Nullable String theRecipient, @Nullable String thePasscode) { 129 Validate.notBlank(thePublicId, "Public ID is required"); 130 131 ShlManifestRequestJson request = new ShlManifestRequestJson(); 132 request.setRecipient(theRecipient); 133 request.setPasscode(thePasscode); 134 135 ShlManifestResponseJson result = myRestClient 136 .post() 137 .uri("shl/manifest/{publicId}", thePublicId) 138 .contentType(MediaType.APPLICATION_JSON) 139 .body(request) 140 .retrieve() 141 .body(ShlManifestResponseJson.class); 142 return Objects.requireNonNull(result); 143 } 144 145 @Nonnull 146 public byte[] fetchFile(@Nonnull String thePublicId, @Nonnull String theFileId) { 147 Validate.notBlank(thePublicId, "Public ID is required"); 148 Validate.notBlank(theFileId, "File ID is required"); 149 150 byte[] result = myRestClient 151 .get() 152 .uri("shl/manifest/{publicId}/file/{fileId}", thePublicId, theFileId) 153 .accept(MediaType.ALL) 154 .retrieve() 155 .body(byte[].class); 156 return Objects.requireNonNull(result); 157 } 158 159 @Override 160 public void close() { 161 if (myOwnership != null) { 162 myOwnership.closeIfOwned(); 163 } 164 } 165}