001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2025 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.test.app.clients; 011 012import ca.cdr.test.app.clients.common.RequestFactoryUtil; 013import ca.uhn.fhir.jpa.packages.PackageInstallOutcomeJson; 014import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; 015import jakarta.annotation.Nonnull; 016import org.apache.commons.lang3.Validate; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019import org.springframework.http.MediaType; 020import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 021import org.springframework.http.client.support.BasicAuthenticationInterceptor; 022import org.springframework.web.client.RestClient; 023 024import java.util.Objects; 025 026/** 027 * NpmPackageClient is a client for the NPM Package Registry API of the CDR. 028 * It currently supports anonymous and http-basic authentication. 029 * 030 * This client provides access to package installation operations through 031 * Smile CDR's package registry endpoint, typically running on port 8002. 032 */ 033public class NpmPackageClient { 034 private static final Logger ourLog = LoggerFactory.getLogger(NpmPackageClient.class); 035 036 private final RestClient myRestClient; 037 038 NpmPackageClient(RestClient theRestClient) { 039 myRestClient = theRestClient; 040 } 041 042 /** 043 * Build an NpmPackageClient with authentication 044 * @param theBaseUrl The base URL of the package registry 045 * @param theUsername Username for authentication 046 * @param thePassword Password for authentication 047 * @return Configured NpmPackageClient 048 */ 049 public static NpmPackageClient build(String theBaseUrl, String theUsername, String thePassword) { 050 RestClient restClient = builderForUrlWithJsonDefault(theBaseUrl) 051 .requestInterceptor(new BasicAuthenticationInterceptor(theUsername, thePassword)) 052 .build(); 053 054 return new NpmPackageClient(restClient); 055 } 056 057 /** 058 * Build an anonymous NpmPackageClient 059 * @param theBaseUrl The base URL of the package registry 060 * @return Configured NpmPackageClient 061 */ 062 public static NpmPackageClient buildAnonymous(String theBaseUrl) { 063 RestClient restClient = builderForUrlWithJsonDefault(theBaseUrl).build(); 064 065 return new NpmPackageClient(restClient); 066 } 067 068 /** 069 * Install a package using the provided specification 070 * @param theSpec The package installation specification 071 * @return The installation outcome 072 */ 073 @Nonnull 074 public PackageInstallOutcomeJson installPackageBySpec(PackageInstallationSpec theSpec) { 075 Validate.notNull(theSpec, "Package specification is required"); 076 Validate.notEmpty(theSpec.getName(), "Package name is required"); 077 Validate.notEmpty(theSpec.getVersion(), "Package version is required"); 078 079 ourLog.info("Installing package {}#{}", theSpec.getName(), theSpec.getVersion()); 080 081 PackageInstallOutcomeJson result = myRestClient 082 .put() 083 .uri("/write/install/by-spec") 084 .body(theSpec) 085 .retrieve() 086 .body(PackageInstallOutcomeJson.class); 087 return Objects.requireNonNull(result); 088 } 089 090 /** 091 * Install a package using the provided specification as a JSON string 092 * @param theSpecJson The package installation specification as JSON string 093 * @return The installation outcome 094 */ 095 @Nonnull 096 public PackageInstallOutcomeJson installPackageBySpec(String theSpecJson) { 097 Validate.notEmpty(theSpecJson, "Package specification JSON is required"); 098 099 ourLog.info("Installing package from JSON specification: {}", theSpecJson); 100 101 PackageInstallOutcomeJson result = myRestClient 102 .put() 103 .uri("/write/install/by-spec") 104 .contentType(MediaType.APPLICATION_JSON) 105 .body(theSpecJson) 106 .retrieve() 107 .body(PackageInstallOutcomeJson.class); 108 return Objects.requireNonNull(result); 109 } 110 111 /** 112 * Create a RestClient builder configured for JSON communication 113 * @param theBaseUrl The base URL 114 * @return Configured RestClient builder 115 */ 116 private static @Nonnull RestClient.Builder builderForUrlWithJsonDefault(String theBaseUrl) { 117 HttpComponentsClientHttpRequestFactory requestFactory = RequestFactoryUtil.buildSmileRequestFactory(); 118 119 return RestClient.builder() 120 .baseUrl(theBaseUrl) 121 .requestFactory(requestFactory) 122 // This sets the default content type to JSON, but allows the actual request to override it. 123 .defaultRequest(r -> r.accept(MediaType.APPLICATION_JSON)); 124 } 125}