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.api.model.json; 011 012import ca.uhn.fhir.model.api.IModelJson; 013import ca.uhn.fhir.model.api.annotation.ExampleSupplier; 014import com.fasterxml.jackson.annotation.JsonProperty; 015import io.swagger.v3.oas.annotations.media.Schema; 016import jakarta.annotation.Nonnull; 017import jakarta.annotation.Nullable; 018 019import java.util.Objects; 020import java.util.function.Supplier; 021 022import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; 023 024@SuppressWarnings("UnusedReturnValue") 025@Schema(name = "TokenizationRule", description = "Defines a single rule for FHIR Repository Tokenization") 026@ExampleSupplier(TokenizationRuleJson.ExampleSupplier.class) 027public class TokenizationRuleJson implements IModelJson, Cloneable { 028 029 @Schema( 030 description = 031 "An optional description of this rule. This field does not affect the functionality of the rule, but can be used as documentation.") 032 @JsonProperty(value = "description", required = false) 033 private String myDescription; 034 035 @Schema( 036 description = 037 "A FHIRPath expression within a resource pointing to the element to tokenize. The value must begin with a resource type, e.g. `Patient.name.family`.") 038 @JsonProperty(value = "path", required = true) 039 private String myPath; 040 041 @JsonProperty(value = "searchParameter", required = false) 042 @Schema( 043 description = 044 "The name of a SearchParameter which can be used to search for the tokenized value. If the FHIRPath expression in this rule matches the path of an existing SearchParameter, it is recommended to use the name of that SearchParameter here. This may be left empty, in which case it will not be possible to perform a search for the tokenized value (in other words, this should only be populated for rules containing paths which should be indexed for searching).") 045 private String mySearchParameter; 046 047 @JsonProperty(value = "searchValueNormalization", required = false) 048 @Schema( 049 description = 050 "If this property is set, the element value will be tokenized twice. The first value contains the original element as it was stored in the resource, and the second contains a normalized version of the element. The normalized version is used for search indexing. See [Search Normalization](/docs/fhir_repository/tokenization.html#search-normalization) for more information.") 051 private TokenizationValueNormalizationEnum mySearchValueNormalization; 052 053 @JsonProperty(value = "neverDetokenize", required = false) 054 @Schema( 055 description = 056 "If set to true, the system will never attempt to de-tokenize the value. This could be set if the configured tokenization algorithm is not reversible, or the usage scenario means that it is not desirable to reverse the tokenization on this element.") 057 private boolean myNeverDetokenize; 058 059 @JsonProperty(value = "status", required = false) 060 @Schema( 061 description = 062 "The status of this rule. If not set, defaults to ACTIVE. Setting the status to QUIESCE means that the rule is still available for detokenization but new data should not be tokenized with this rule. See [Updating Tokenization Rules](/docs/fhir_repository/tokenization.html#update-tokenization-rules) for more information.") 063 private RuleStatusEnum myStatus; 064 065 /** 066 * Constructor 067 */ 068 public TokenizationRuleJson() { 069 super(); 070 } 071 072 /** 073 * Copy Constructor 074 */ 075 public TokenizationRuleJson(TokenizationRuleJson theTokenizationRuleJson) { 076 myDescription = theTokenizationRuleJson.myDescription; 077 myPath = theTokenizationRuleJson.myPath; 078 mySearchParameter = theTokenizationRuleJson.mySearchParameter; 079 mySearchValueNormalization = theTokenizationRuleJson.mySearchValueNormalization; 080 myNeverDetokenize = theTokenizationRuleJson.myNeverDetokenize; 081 myStatus = theTokenizationRuleJson.myStatus; 082 } 083 084 /** 085 * Defaults to {@link RuleStatusEnum#ACTIVE} if not set 086 * @since 2025.11 087 */ 088 @Nonnull 089 public RuleStatusEnum getStatus() { 090 return defaultIfNull(myStatus, RuleStatusEnum.ACTIVE); 091 } 092 093 public TokenizationRuleJson setStatus(RuleStatusEnum theStatus) { 094 myStatus = theStatus; 095 return this; 096 } 097 098 public String getDescription() { 099 return myDescription; 100 } 101 102 public TokenizationRuleJson setDescription(String theDescription) { 103 myDescription = theDescription; 104 return this; 105 } 106 107 public String getPath() { 108 return myPath; 109 } 110 111 public TokenizationRuleJson setPath(String thePath) { 112 myPath = thePath; 113 return this; 114 } 115 116 public String getSearchParameter() { 117 return mySearchParameter; 118 } 119 120 public TokenizationRuleJson setSearchParameter(String theSearchParameter) { 121 mySearchParameter = theSearchParameter; 122 return this; 123 } 124 125 @Nullable 126 public TokenizationValueNormalizationEnum getSearchValueNormalization() { 127 return mySearchValueNormalization; 128 } 129 130 public TokenizationRuleJson setSearchValueNormalization( 131 TokenizationValueNormalizationEnum theSearchValueNormalization) { 132 mySearchValueNormalization = theSearchValueNormalization; 133 return this; 134 } 135 136 @Nullable 137 public String getResourceType() { 138 int dotIdx = myPath.indexOf('.'); 139 if (dotIdx != -1) { 140 return myPath.substring(0, dotIdx); 141 } 142 return null; 143 } 144 145 public boolean isNeverDetokenize() { 146 return myNeverDetokenize; 147 } 148 149 public TokenizationRuleJson setNeverDetokenize(boolean theNeverDetokenize) { 150 myNeverDetokenize = theNeverDetokenize; 151 return this; 152 } 153 154 @SuppressWarnings("MethodDoesntCallSuperMethod") 155 @Override 156 public TokenizationRuleJson clone() { 157 return new TokenizationRuleJson(this); 158 } 159 160 @Override 161 public boolean equals(Object theO) { 162 if (!(theO instanceof TokenizationRuleJson that)) { 163 return false; 164 } 165 return myNeverDetokenize == that.myNeverDetokenize 166 && Objects.equals(myDescription, that.myDescription) 167 && Objects.equals(myPath, that.myPath) 168 && Objects.equals(mySearchParameter, that.mySearchParameter) 169 && mySearchValueNormalization == that.mySearchValueNormalization; 170 } 171 172 @Override 173 public int hashCode() { 174 return Objects.hash(myDescription, myPath, mySearchParameter, mySearchValueNormalization, myNeverDetokenize); 175 } 176 177 @Override 178 public String toString() { 179 return "TokenizationRuleJson[" + "path='" 180 + myPath + '\'' + ", description='" 181 + myDescription + '\'' + ", searchParameter='" 182 + mySearchParameter + '\'' + ", searchValueNormalization=" 183 + mySearchValueNormalization + ", neverDetokenize=" 184 + myNeverDetokenize + ']'; 185 } 186 187 public enum RuleStatusEnum { 188 /** 189 * Rule is active - this is the default 190 */ 191 @Schema(description = "Rule is active - this is the default.") 192 ACTIVE, 193 194 /** 195 * Rule is being disabled: existing data stored with this rule can be detokenized, but new data 196 * should not be tokenized with this rule. 197 */ 198 @Schema( 199 description = 200 "Rule is being disabled: existing data stored with this rule can be detokenized, but new data should not be tokenized with this rule.") 201 QUIESCE, 202 203 /** 204 * Rule is completely ignored 205 */ 206 @Schema(description = "Rule is completely ignored.") 207 DISABLED 208 } 209 210 public static class ExampleSupplier implements Supplier<TokenizationRuleJson> { 211 @Override 212 public TokenizationRuleJson get() { 213 TokenizationRuleJson rule = new TokenizationRuleJson(); 214 rule.setPath("Patient.name.family"); 215 rule.setDescription("Patient family name"); 216 rule.setSearchParameter("family"); 217 rule.setSearchValueNormalization(TokenizationValueNormalizationEnum.STRING); 218 rule.setStatus(TokenizationRuleJson.RuleStatusEnum.ACTIVE); 219 return rule; 220 } 221 } 222}