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.api.consent; 011 012import ca.cdr.api.model.json.UserSessionDetailsJson; 013import jakarta.annotation.Nullable; 014import org.hl7.fhir.instance.model.api.IBaseResource; 015 016import java.util.ArrayList; 017import java.util.Collections; 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021 022/** 023 * A consent policy request for parameterized policies that accept query parameters. 024 * 025 * <p>Contains: 026 * <ol> 027 * <li>The policy name which should be mapped to a new instance of an {@link ca.uhn.fhir.rest.server.interceptor.consent.IConsentService}</li> 028 * <li>The {@link UserSessionDetailsJson} of the user making the request (may be null during bootstrap validation).</li> 029 * <li>A map of parameters with AND/OR structure that can be used to configure the consent service.</li> 030 * <li>An optional Consent resource for consent-resource-based policies.</li> 031 * </ol> 032 * 033 * <p>Parameterized policies support query parameters that configure their behavior. Parameters are parsed into 034 * an AND/OR structure where: 035 * <ul> 036 * <li>Comma-separated values within a single parameter create OR conditions</li> 037 * <li>Repeated parameter occurrences (separated by &) create AND groups</li> 038 * </ul> 039 * 040 * <p>Example: {@code RedactFhirPathsWhen?_type=Patient,Observation&fhirpath=Patient.name&fhirpath=Patient.address} 041 * 042 * <p>For fixed policies that do not accept parameters, use {@link ConsentFixedPolicyRequest} instead. 043 */ 044// Created by claude-opus-4-5-20251101 045public class ConsentParameterizedPolicyRequest { 046 private final String myPolicyName; 047 private final UserSessionDetailsJson myUserSessionDetailsJson; 048 private final Map<String, List<List<String>>> myParameters; 049 private IBaseResource myConsentResource; 050 051 public ConsentParameterizedPolicyRequest( 052 String thePolicyNameWithQueryParams, UserSessionDetailsJson theUserSessionDetails) { 053 ParameterizedPolicyQueryParameterParser parsedPolicy = 054 ParameterizedPolicyQueryParameterParser.parse(thePolicyNameWithQueryParams); 055 myPolicyName = parsedPolicy.getBasePolicyName(); 056 myUserSessionDetailsJson = theUserSessionDetails; 057 // Create a mutable copy so addParameter() can modify it 058 myParameters = new HashMap<>(parsedPolicy.getAllParameters()); 059 } 060 061 public String getPolicyName() { 062 return myPolicyName; 063 } 064 065 public UserSessionDetailsJson getUserSessionDetailsJson() { 066 return myUserSessionDetailsJson; 067 } 068 069 /** 070 * Add a parameter value. The value is split on commas to create an OR list, 071 * and each call creates a new AND group for the key. 072 * This supports multi-valued query parameters like {@code fhirpath=X,Y&fhirpath=Z} 073 * where X,Y is an OR list and Z is a separate AND group. 074 * 075 * @param theKey the parameter key 076 * @param theValue the parameter value (comma-separated values become OR conditions) 077 */ 078 public void addParameter(String theKey, String theValue) { 079 List<String> orList = splitAndTrim(theValue); 080 myParameters.computeIfAbsent(theKey, k -> new ArrayList<>()).add(orList); 081 } 082 083 /** 084 * Add a pre-split OR list as a new AND group for the key. 085 * This is used when parameters have already been parsed and split. 086 * 087 * @param theKey the parameter key 088 * @param theOrList the pre-split OR values 089 */ 090 public void addParameterOrList(String theKey, List<String> theOrList) { 091 myParameters.computeIfAbsent(theKey, k -> new ArrayList<>()).add(new ArrayList<>(theOrList)); 092 } 093 094 private List<String> splitAndTrim(String theValue) { 095 if (theValue == null || theValue.isEmpty()) { 096 return Collections.emptyList(); 097 } 098 List<String> result = new ArrayList<>(); 099 for (String part : theValue.split(",")) { 100 String trimmed = part.trim(); 101 if (!trimmed.isEmpty()) { 102 result.add(trimmed); 103 } 104 } 105 return result; 106 } 107 108 /** 109 * Check if a parameter exists. 110 * 111 * @param theKey the parameter key 112 * @return true if the parameter exists 113 */ 114 public boolean hasParameter(String theKey) { 115 List<List<String>> andLists = myParameters.get(theKey); 116 return andLists != null && !andLists.isEmpty(); 117 } 118 119 /** 120 * Get the AND/OR structure for a parameter. 121 * The outer list represents AND groups (each occurrence of the parameter). 122 * The inner list represents OR values (comma-separated within one occurrence). 123 * 124 * @param theKey the parameter key 125 * @return list of AND groups, each containing OR values, or empty list if not found 126 */ 127 public List<List<String>> getParameter(String theKey) { 128 List<List<String>> andLists = myParameters.get(theKey); 129 return andLists != null ? Collections.unmodifiableList(andLists) : Collections.emptyList(); 130 } 131 132 /** 133 * Get all parameters with their AND/OR structure. 134 * 135 * @return unmodifiable map of parameter keys to lists of AND groups 136 */ 137 public Map<String, List<List<String>>> getAllParameters() { 138 return Collections.unmodifiableMap(myParameters); 139 } 140 141 /** 142 * Get the Consent resource associated with this request. 143 * This is only set for consent-resource-based policies. 144 * 145 * @return the Consent resource, or null for builtin policies 146 */ 147 @Nullable 148 public IBaseResource getConsentResource() { 149 return myConsentResource; 150 } 151 152 /** 153 * Set the Consent resource for consent-resource-based policies. 154 * 155 * @param theConsentResource the Consent resource 156 * @return this request for method chaining 157 */ 158 public ConsentParameterizedPolicyRequest setConsentResource(@Nullable IBaseResource theConsentResource) { 159 myConsentResource = theConsentResource; 160 return this; 161 } 162}