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 &amp;) 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}