001package ca.cdr.test.app.clients; 002/*- 003 * #%L 004 * Smile CDR - CDR 005 * %% 006 * Copyright (C) 2016 - 2025 Smile CDR, Inc. 007 * %% 008 * All rights reserved. 009 * #L% 010 */ 011 012import ca.cdr.api.model.json.OAuth2ClientDetailsJson; 013import ca.cdr.api.model.json.OAuth2WritableClientDetailsJson; 014import ca.cdr.api.model.json.TransactionLogEventsJson; 015import ca.cdr.api.model.json.UserDetailsJson; 016import ca.cdr.test.model.NodeConfigurations; 017import ca.uhn.fhir.context.FhirContext; 018import ca.uhn.fhir.context.FhirVersionEnum; 019import ca.uhn.fhir.rest.api.Constants; 020import com.fasterxml.jackson.core.JsonProcessingException; 021import com.fasterxml.jackson.databind.JsonNode; 022import com.fasterxml.jackson.databind.ObjectMapper; 023import com.google.common.base.Charsets; 024import jakarta.annotation.Nonnull; 025import org.apache.commons.lang3.Validate; 026import org.hl7.fhir.instance.model.api.IBaseParameters; 027import org.hl7.fhir.instance.model.api.IBaseResource; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 031import org.springframework.web.client.RestClient; 032 033import java.util.Base64; 034import java.util.LinkedHashMap; 035import java.util.Map; 036import java.util.Objects; 037 038import static org.springframework.http.HttpHeaders.CONTENT_TYPE; 039import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 040 041// Many new methods generated by Claude Sonnet 3.7 042 043// TODO KHS test unused methods 044public class AdminJsonRestClient { 045 private static final Logger ourLog = LoggerFactory.getLogger(AdminJsonRestClient.class); 046 public static final int DEFAULT_FHIR_ENDPOINT_PORT = 8000; 047 048 final RestClient myRestClient; 049 050 AdminJsonRestClient(RestClient theRestClient) { 051 myRestClient = theRestClient; 052 } 053 054 public static AdminJsonRestClient build(String theBaseUrl, String theUsername, String thePassword) { 055 MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); 056 RestClient restClient = RestClient.builder() 057 .baseUrl(theBaseUrl) 058 .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) 059 .defaultHeader( 060 Constants.HEADER_AUTHORIZATION, 061 "Basic " 062 + Base64.getEncoder() 063 .encodeToString((theUsername + ":" + thePassword).getBytes(Charsets.UTF_8))) 064 .build(); 065 066 return new AdminJsonRestClient(restClient); 067 } 068 069 public static AdminJsonRestClient buildAnonymous(String theBaseUrl) { 070 RestClient restClient = RestClient.builder() 071 .baseUrl(theBaseUrl) 072 .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) 073 .build(); 074 075 return new AdminJsonRestClient(restClient); 076 } 077 078 @Nonnull 079 public UserDetailsJson userCreate(String theNode, String theModuleId, UserDetailsJson userDetails) { 080 Validate.notEmpty(theNode, "Node ID is required"); 081 Validate.notEmpty(theModuleId, "Module ID is required"); 082 Validate.notNull(userDetails, "User does not have an assigned pid. Use userCreate() to create users."); 083 UserDetailsJson result = myRestClient 084 .post() 085 .uri("user-management/{nodeId}/{moduleId}", theNode, theModuleId) 086 .body(userDetails) 087 .retrieve() 088 .body(UserDetailsJson.class); 089 return Objects.requireNonNull(result); 090 } 091 092 @Nonnull 093 public UserDetailsJson userUpdate(UserDetailsJson theUserDetails) { 094 Validate.notEmpty(theUserDetails.getNodeId(), "Node ID is required"); 095 Validate.notEmpty(theUserDetails.getModuleId(), "Module ID is required"); 096 Validate.notNull( 097 theUserDetails.getPid(), "User does not have an assigned pid. Use userCreate() to create users."); 098 UserDetailsJson result = myRestClient 099 .put() 100 .uri( 101 "user-management/{nodeId}/{moduleId}/{userId}", 102 theUserDetails.getNodeId(), 103 theUserDetails.getModuleId(), 104 theUserDetails.getPid()) 105 .body(theUserDetails) 106 .retrieve() 107 .body(UserDetailsJson.class); 108 return Objects.requireNonNull(result); 109 } 110 111 @Nonnull 112 public JsonNode userFindByUsername(String theNodeId, String theModuleId, String theUsername) { 113 Validate.notEmpty(theNodeId, "Node ID is required"); 114 Validate.notEmpty(theModuleId, "Module ID is required"); 115 Validate.notEmpty(theUsername, "username is required"); 116 JsonNode result = myRestClient 117 .get() 118 .uri("user-management/{nodeId}/{moduleId}?searchTerm={username}", theNodeId, theModuleId, theUsername) 119 .retrieve() 120 .body(JsonNode.class); 121 return Objects.requireNonNull(result); 122 } 123 124 /** 125 * Find users by username and return the result as a String that can be used with ObjectMapper 126 * 127 * @param theNodeId The node ID 128 * @param theModuleId The module ID 129 * @param theUsername The username to search for 130 * @return The JSON string representation of the result 131 */ 132 @Nonnull 133 public String userFindByUsernameAsString(String theNodeId, String theModuleId, String theUsername) { 134 JsonNode result = userFindByUsername(theNodeId, theModuleId, theUsername); 135 return result.toString(); 136 } 137 138 @Nonnull 139 public JsonNode userFindAll(String theNodeId, String theModuleId, int thePageSize) { 140 Validate.notEmpty(theNodeId, "Node ID is required"); 141 Validate.notEmpty(theModuleId, "Module ID is required"); 142 JsonNode result = myRestClient 143 .get() 144 .uri("user-management/{nodeId}/{moduleId}?pageSize={pageSize}", theNodeId, theModuleId, thePageSize) 145 .retrieve() 146 .body(JsonNode.class); 147 return Objects.requireNonNull(result); 148 } 149 150 /** 151 * Find all users and return the result as a String that can be used with ObjectMapper 152 * 153 * @param theNodeId The node ID 154 * @param theModuleId The module ID 155 * @param thePageSize The page size 156 * @return The JSON string representation of the result 157 */ 158 @Nonnull 159 public String userFindAllAsString(String theNodeId, String theModuleId, int thePageSize) { 160 JsonNode result = userFindAll(theNodeId, theModuleId, thePageSize); 161 return result.toString(); 162 } 163 164 @Nonnull 165 public UserDetailsJson userFetchByPid(String theNodeId, String theModuleId, Long thePid) { 166 Validate.notEmpty(theNodeId, "Node ID is required"); 167 Validate.notEmpty(theModuleId, "Module ID is required"); 168 Validate.notNull(thePid, "User does not have an assigned pid. Use userCreate() to create users."); 169 UserDetailsJson result = myRestClient 170 .get() 171 .uri("user-management/{nodeId}/{moduleId}/{userId}", theNodeId, theModuleId, thePid) 172 .retrieve() 173 .body(UserDetailsJson.class); 174 return Objects.requireNonNull(result); 175 } 176 177 /** 178 * Get the module configuration for a specific node and module 179 */ 180 @Nonnull 181 public JsonNode getModuleConfig(String theNodeId, String theModuleId) { 182 Validate.notEmpty(theNodeId, "Node ID is required"); 183 Validate.notEmpty(theModuleId, "Module ID is required"); 184 JsonNode result = myRestClient 185 .get() 186 .uri("module-config/{nodeId}/{moduleId}", theNodeId, theModuleId) 187 .retrieve() 188 .body(JsonNode.class); 189 return Objects.requireNonNull(result); 190 } 191 192 /** 193 * Update the configuration for a specific module 194 */ 195 @Nonnull 196 public JsonNode updateModuleConfig( 197 String theNodeId, 198 String theModuleId, 199 JsonNode theOptions, 200 boolean theRestart, 201 boolean theReload) { 202 Validate.notEmpty(theNodeId, "Node ID is required"); 203 Validate.notEmpty(theModuleId, "Module ID is required"); 204 Validate.notNull(theOptions, "Options is required"); 205 206 String uri = String.format( 207 "module-config/%s/%s/set?restart=%s&reload=%s", theNodeId, theModuleId, theRestart, theReload); 208 JsonNode result = 209 myRestClient.put().uri(uri).body(theOptions).retrieve().body(JsonNode.class); 210 return Objects.requireNonNull(result); 211 } 212 213 /** 214 * Request to start a module on all processes 215 */ 216 @Nonnull 217 private JsonNode startModule(String theNodeId, String theModuleId) { 218 Validate.notEmpty(theNodeId, "Node ID is required"); 219 Validate.notEmpty(theModuleId, "Module ID is required"); 220 JsonNode result = myRestClient 221 .post() 222 .uri("module-config/{nodeId}/{moduleId}/start", theNodeId, theModuleId) 223 .retrieve() 224 .body(JsonNode.class); 225 return Objects.requireNonNull(result); 226 } 227 228 /** 229 * Request to stop a module on all processes 230 * ModuleProcessesStatusChangeResponseJson 231 */ 232 @Nonnull 233 public JsonNode stopModule(String theNodeId, String theModuleId) { 234 Validate.notEmpty(theNodeId, "Node ID is required"); 235 Validate.notEmpty(theModuleId, "Module ID is required"); 236 JsonNode result = myRestClient 237 .post() 238 .uri("module-config/{nodeId}/{moduleId}/stop", theNodeId, theModuleId) 239 .retrieve() 240 .body(JsonNode.class); 241 return Objects.requireNonNull(result); 242 } 243 244 /** 245 * Request to restart a module on all processes 246 */ 247 @Nonnull 248 public JsonNode restartModule(String theNodeId, String theModuleId) { 249 Validate.notEmpty(theNodeId, "Node ID is required"); 250 Validate.notEmpty(theModuleId, "Module ID is required"); 251 JsonNode result = myRestClient 252 .post() 253 .uri("module-config/{nodeId}/{moduleId}/restart", theNodeId, theModuleId) 254 .retrieve() 255 .body(JsonNode.class); 256 return Objects.requireNonNull(result); 257 } 258 259 /** 260 * Get a list of restore points for a node 261 */ 262 @Nonnull 263 public JsonNode getRestorePoints( 264 String theNodeId, String theVersion, String theFromDate, String theToDate, int theOffset, int theCount) { 265 Validate.notEmpty(theNodeId, "Node ID is required"); 266 267 StringBuilder uriBuilder = 268 new StringBuilder("module-config/").append(theNodeId).append("/restorePoints"); 269 270 Map<String, String> params = new LinkedHashMap<>(); 271 272 if (theVersion != null) { 273 params.put("version", theVersion); 274 } 275 if (theFromDate != null) { 276 params.put("from", theFromDate); 277 } 278 if (theToDate != null) { 279 params.put("to", theToDate); 280 } 281 params.put("offset", String.valueOf(theOffset)); 282 params.put("count", String.valueOf(theCount)); 283 284 uriBuilder.append('?'); 285 params.entrySet().stream() 286 .map(e -> e.getKey() + "=" + e.getValue()) 287 .reduce((a, b) -> a + "&" + b) 288 .ifPresent(uriBuilder::append); 289 290 JsonNode result = 291 myRestClient.get().uri(uriBuilder.toString()).retrieve().body(JsonNode.class); 292 return Objects.requireNonNull(result); 293 } 294 295 /** 296 * Get a specific restore point 297 */ 298 @Nonnull 299 public JsonNode getRestorePoint(String theNodeId, Long theId) { 300 Validate.notEmpty(theNodeId, "Node ID is required"); 301 Validate.notNull(theId, "Restore point ID is required"); 302 JsonNode result = myRestClient 303 .get() 304 .uri("module-config/{nodeId}/restorePoints/{id}", theNodeId, theId) 305 .retrieve() 306 .body(JsonNode.class); 307 return Objects.requireNonNull(result); 308 } 309 310 /** 311 * Restore the system to a specific restore point 312 */ 313 public void restoreSystem(String theNodeId, Long theRestorePointId) { 314 Validate.notEmpty(theNodeId, "Node ID is required"); 315 Validate.notNull(theRestorePointId, "Restore point ID is required"); 316 myRestClient 317 .post() 318 .uri("module-config/{nodeId}/restorePoints/{id}/restore", theNodeId, theRestorePointId) 319 .retrieve() 320 .toBodilessEntity(); 321 } 322 323 /** 324 * Get health checks for all modules 325 */ 326 @Nonnull 327 public JsonNode getHealthChecks(boolean theOnlyRunning) { 328 String uri = "runtime-status/node-statuses/health-checks" + (theOnlyRunning ? "?onlyRunning=true" : ""); 329 JsonNode result = myRestClient.get().uri(uri).retrieve().body(JsonNode.class); 330 return Objects.requireNonNull(result); 331 } 332 333 /** 334 * Get node statuses 335 */ 336 @Nonnull 337 public JsonNode getNodeStatuses() { 338 JsonNode result = myRestClient 339 .get() 340 .uri("runtime-status/node-statuses/complete") 341 .retrieve() 342 .body(JsonNode.class); 343 return Objects.requireNonNull(result); 344 } 345 346 /** 347 * Cancel a batch job 348 */ 349 public void cancelBatchJob(String theModuleId, String theJobId) { 350 Validate.notEmpty(theModuleId, "Module ID is required"); 351 Validate.notEmpty(theJobId, "Job ID is required"); 352 myRestClient 353 .get() 354 .uri("batch2-jobs/modules/{moduleId}/jobs/{jobId}/cancel", theModuleId, theJobId) 355 .retrieve() 356 .toBodilessEntity(); 357 } 358 359 /** 360 * Process a bulk ETL import file 361 */ 362 public String processBulkImport(String theModuleId, String theFilename, String theContent) { 363 Validate.notEmpty(theModuleId, "Module ID is required"); 364 Validate.notEmpty(theFilename, "Filename is required"); 365 Validate.notNull(theContent, "Content is required"); 366 367 String result = myRestClient 368 .post() 369 .uri("bulk-import/process-etl-file/{moduleId}?filename={filename}", theModuleId, theFilename) 370 .header("Content-Type", "text/plain") 371 .body(theContent) 372 .retrieve() 373 .body(String.class); 374 375 return result; 376 } 377 378 /** 379 * Get transaction logs 380 */ 381 @Nonnull 382 public TransactionLogEventsJson getTransactionLogs() { 383 TransactionLogEventsJson result = 384 myRestClient.get().uri("transaction-log").retrieve().body(TransactionLogEventsJson.class); 385 return Objects.requireNonNull(result); 386 } 387 388 /** 389 * Get a specific transaction log event with its body 390 */ 391 @Nonnull 392 public TransactionLogEventsJson.TransactionLogEventJson getTransactionLogEvent( 393 Long theEventId, boolean theIncludeBody) { 394 Validate.notNull(theEventId, "Event ID is required"); 395 396 String uri = "transaction-log/event/" + theEventId; 397 if (theIncludeBody) { 398 uri += "?includeBody=true"; 399 } 400 401 TransactionLogEventsJson.TransactionLogEventJson result = 402 myRestClient.get().uri(uri).retrieve().body(TransactionLogEventsJson.TransactionLogEventJson.class); 403 return Objects.requireNonNull(result); 404 } 405 406 /** 407 * Create an OAuth client for a specific module 408 */ 409 @Nonnull 410 public OAuth2ClientDetailsJson createOAuthClient( 411 String theNodeId, String theModuleId, OAuth2WritableClientDetailsJson theClientDetails) { 412 Validate.notEmpty(theNodeId, "Node ID is required"); 413 Validate.notEmpty(theModuleId, "Module ID is required"); 414 Validate.notNull(theClientDetails, "Client details are required"); 415 416 OAuth2ClientDetailsJson result = myRestClient 417 .post() 418 .uri("openid-connect-clients/{nodeId}/{moduleId}", theNodeId, theModuleId) 419 .body(theClientDetails) 420 .retrieve() 421 .body(OAuth2ClientDetailsJson.class); 422 return Objects.requireNonNull(result); 423 } 424 425 /** 426 * Update an OAuth client for a specific module 427 */ 428 @Nonnull 429 public OAuth2ClientDetailsJson updateOAuthClient( 430 String theNodeId, 431 String theModuleId, 432 String theClientId, 433 OAuth2WritableClientDetailsJson theClientDetails) { 434 Validate.notEmpty(theNodeId, "Node ID is required"); 435 Validate.notEmpty(theModuleId, "Module ID is required"); 436 Validate.notEmpty(theClientId, "Client ID is required"); 437 Validate.notNull(theClientDetails, "Client details are required"); 438 439 OAuth2ClientDetailsJson result = myRestClient 440 .put() 441 .uri("openid-connect-clients/{nodeId}/{moduleId}/{clientId}", theNodeId, theModuleId, theClientId) 442 .body(theClientDetails) 443 .retrieve() 444 .body(OAuth2ClientDetailsJson.class); 445 return Objects.requireNonNull(result); 446 } 447 448 /** 449 * Delete an OAuth client 450 */ 451 public void deleteOAuthClient(String theNodeId, String theModuleId, String theClientId) { 452 Validate.notEmpty(theNodeId, "Node ID is required"); 453 Validate.notEmpty(theModuleId, "Module ID is required"); 454 Validate.notEmpty(theClientId, "Client ID is required"); 455 456 myRestClient 457 .delete() 458 .uri("openid-connect-clients/{nodeId}/{moduleId}/{clientId}", theNodeId, theModuleId, theClientId) 459 .retrieve() 460 .toBodilessEntity(); 461 } 462 463 /** 464 * Get an OAuth client 465 */ 466 @Nonnull 467 public OAuth2ClientDetailsJson getOAuthClient(String theNodeId, String theModuleId, String theClientId) { 468 Validate.notEmpty(theNodeId, "Node ID is required"); 469 Validate.notEmpty(theModuleId, "Module ID is required"); 470 Validate.notEmpty(theClientId, "Client ID is required"); 471 472 OAuth2ClientDetailsJson result = myRestClient 473 .get() 474 .uri("openid-connect-clients/{nodeId}/{moduleId}/{clientId}", theNodeId, theModuleId, theClientId) 475 .retrieve() 476 .body(OAuth2ClientDetailsJson.class); 477 return Objects.requireNonNull(result); 478 } 479 480 /** 481 * Create an OIDC server for a specific module 482 */ 483 @Nonnull 484 public JsonNode createOidcServer(String theNodeId, String theModuleId, JsonNode theServerDetails) { 485 Validate.notEmpty(theNodeId, "Node ID is required"); 486 Validate.notEmpty(theModuleId, "Module ID is required"); 487 Validate.notNull(theServerDetails, "Server details are required"); 488 489 JsonNode result = myRestClient 490 .post() 491 .uri("openid-connect-servers/{nodeId}/{moduleId}", theNodeId, theModuleId) 492 .body(theServerDetails) 493 .retrieve() 494 .body(JsonNode.class); 495 return Objects.requireNonNull(result); 496 } 497 498 /** 499 * Get an OIDC server 500 */ 501 @Nonnull 502 public JsonNode getOidcServer(String theNodeId, String theModuleId, String theServerId) { 503 Validate.notEmpty(theNodeId, "Node ID is required"); 504 Validate.notEmpty(theModuleId, "Module ID is required"); 505 Validate.notEmpty(theServerId, "Server ID is required"); 506 507 JsonNode result = myRestClient 508 .get() 509 .uri("openid-connect-servers/{nodeId}/{moduleId}/{serverId}", theNodeId, theModuleId, theServerId) 510 .retrieve() 511 .body(JsonNode.class); 512 return Objects.requireNonNull(result); 513 } 514 515 /** 516 * Update an OIDC server 517 */ 518 @Nonnull 519 public JsonNode updateOidcServer( 520 String theNodeId, String theModuleId, String theServerId, JsonNode theServerDetails) { 521 Validate.notEmpty(theNodeId, "Node ID is required"); 522 Validate.notEmpty(theModuleId, "Module ID is required"); 523 Validate.notEmpty(theServerId, "Server ID is required"); 524 Validate.notNull(theServerDetails, "Server details are required"); 525 526 JsonNode result = myRestClient 527 .put() 528 .uri("openid-connect-servers/{nodeId}/{moduleId}/{serverId}", theNodeId, theModuleId, theServerId) 529 .body(theServerDetails) 530 .retrieve() 531 .body(JsonNode.class); 532 return Objects.requireNonNull(result); 533 } 534 535 /** 536 * Get MDM links 537 */ 538 @Nonnull 539 public JsonNode getMdmLinks(String theModuleId) { 540 Validate.notEmpty(theModuleId, "Module ID is required"); 541 542 JsonNode result = myRestClient 543 .get() 544 .uri("mdm/{moduleId}/query-links", theModuleId) 545 .retrieve() 546 .body(JsonNode.class); 547 return Objects.requireNonNull(result); 548 } 549 550 /** 551 * Merge MDM golden resources 552 */ 553 @Nonnull 554 public JsonNode mergeMdmGoldenResources(String theModuleId, Object theRequest) { 555 Validate.notEmpty(theModuleId, "Module ID is required"); 556 Validate.notNull(theRequest, "Request is required"); 557 558 JsonNode result = myRestClient 559 .post() 560 .uri("mdm/{moduleId}/merge-golden-resources", theModuleId) 561 .body(theRequest) 562 .retrieve() 563 .body(JsonNode.class); 564 return Objects.requireNonNull(result); 565 } 566 567 /** 568 * Update MDM link 569 */ 570 @Nonnull 571 public JsonNode updateMdmLink(String theModuleId, Object theRequest) { 572 Validate.notEmpty(theModuleId, "Module ID is required"); 573 Validate.notNull(theRequest, "Request is required"); 574 575 JsonNode result = myRestClient 576 .post() 577 .uri("mdm/{moduleId}/update-link", theModuleId) 578 .body(theRequest) 579 .retrieve() 580 .body(JsonNode.class); 581 return Objects.requireNonNull(result); 582 } 583 584 /** 585 * Get MDM duplicate golden resources 586 */ 587 @Nonnull 588 public JsonNode getMdmDuplicateGoldenResources(String theModuleId) { 589 Validate.notEmpty(theModuleId, "Module ID is required"); 590 591 JsonNode result = myRestClient 592 .get() 593 .uri("mdm/{moduleId}/duplicate-golden-resources", theModuleId) 594 .retrieve() 595 .body(JsonNode.class); 596 return Objects.requireNonNull(result); 597 } 598 599 /** 600 * Submit an MDM operation to perform batch matching on all resources 601 * 602 * @param theModuleId The module ID 603 * @param theParameters The parameters for the MDM operation 604 * @return The response as a String 605 */ 606 @Nonnull 607 public String submitMdmOperation(String theModuleId, IBaseParameters theParameters) { 608 Validate.notEmpty(theModuleId, "Module ID is required"); 609 610 String body = toJsonString(theParameters); 611 612 String result = myRestClient 613 .post() 614 .uri("{moduleId}/mdm-submit", theModuleId) 615 .body(body) 616 .retrieve() 617 .body(String.class); 618 619 return result != null ? result : ""; 620 } 621 622 public static String toJsonString(IBaseResource theResource) { 623 FhirVersionEnum version = FhirVersionEnum.determineVersionForType(theResource.getClass()); 624 FhirContext ctx = FhirContext.forCached(version); 625 return ctx.newJsonParser().encodeResourceToString(theResource); 626 } 627 628 /** 629 * Exception thrown when a forbidden operation is attempted 630 */ 631 public static class ForbiddenOperationException extends RuntimeException { 632 public ForbiddenOperationException(String message, Throwable cause) { 633 super(message, cause); 634 } 635 } 636 637 /** 638 * Get the node configurations from the admin API. 639 * 640 * @return The node configurations 641 */ 642 @Nonnull 643 public NodeConfigurations getNodeConfigurations() { 644 NodeConfigurations result = myRestClient 645 .get() 646 .uri("/module-config/") 647 .retrieve() 648 .body(NodeConfigurations.class); 649 return Objects.requireNonNull(result); 650 } 651 652 /** 653 * Get the port number for a specific module from the node configurations. 654 * 655 * @param theModuleId The ID of the module to get the port for 656 * @return The port number, or the default FHIR port (8000) if not found 657 */ 658 public int getPortFromModule(String theModuleId) { 659 Validate.notEmpty(theModuleId, "Module ID is required"); 660 NodeConfigurations config = getNodeConfigurations(); 661 662 // Find the first node (assuming there's only one node in the container) 663 if (config.getNodes().isEmpty()) { 664 throw new IllegalStateException("No nodes found in node configuration"); 665 } else { 666 NodeConfigurations.NodeConfiguration node = config.getNodes().get(0); 667 668 // Find the module with the given ID 669 return node.getModule(theModuleId) 670 .map(module -> { 671 // Look for the port property in the module's configuration 672 for (NodeConfigurations.ModuleConfigProperty property : module.getConfigProperties()) { 673 if (property.getKey().equals("port")) { 674 try { 675 return Integer.parseInt(property.getValue()); 676 } catch (NumberFormatException e) { 677 // If the port is not a valid integer, fail. 678 throw new IllegalArgumentException("Invalid port number: " + property.getValue(), e); 679 } 680 } 681 } 682 // If no port property is found, return the default FHIR port 683 return null; 684 }) 685 .orElseThrow(() -> new IllegalArgumentException("Module with ID " + theModuleId + " not found in node configuration")); 686 } 687 688 } 689 690 /** 691 * Since many methods currently return JsonNode, if you have a known model you would like to bind to, you can convert a json object to it using this method 692 */ 693 public <T> T convertJsonNodeToModel(JsonNode jsonNode, Class<T> modelClass) throws JsonProcessingException { 694 try { 695 ObjectMapper objectMapper = new ObjectMapper(); 696 // Direct conversion from JsonNode to model class without string conversion 697 return objectMapper.treeToValue(jsonNode, modelClass); 698 } catch (JsonProcessingException e) { 699 // Log the error with the actual JSON content for debugging 700 ourLog.error("Failed to convert JsonNode to " + modelClass.getSimpleName() + 701 ". JSON content: " + jsonNode.toString(), e); 702 throw e; 703 } 704 } 705 706 public <T> JsonNode convertModelToJsonNode(T model) throws JsonProcessingException { 707 try { 708 ObjectMapper objectMapper = new ObjectMapper(); 709 // Direct conversion from model to JsonNode without going through string 710 return objectMapper.valueToTree(model); 711 } catch (Exception e) { 712 throw new JsonProcessingException("Error converting model to JsonNode") {} ; 713 } 714 } 715 716}