
001/*- 002 * #%L 003 * HAPI FHIR Subscription Server 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.topic; 021 022import ca.uhn.fhir.subscription.SubscriptionConstants; 023import ca.uhn.hapi.converters.canonical.SubscriptionTopicCanonicalizer; 024import org.hl7.fhir.r4.model.Basic; 025import org.hl7.fhir.r4.model.BooleanType; 026import org.hl7.fhir.r4.model.CodeableConcept; 027import org.hl7.fhir.r4.model.Coding; 028import org.hl7.fhir.r4.model.DateTimeType; 029import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 030import org.hl7.fhir.r4.model.Extension; 031import org.hl7.fhir.r4.model.MarkdownType; 032import org.hl7.fhir.r4.model.StringType; 033import org.hl7.fhir.r4.model.UriType; 034 035import java.util.Date; 036 037/** 038 * Builder class for creating and configuring FHIR R4 SubscriptionTopic resources. 039 * <p/> 040 * In R4, SubscriptionTopic is represented as a Basic resource with extensions, 041 * following the pattern outlined in the FHIR Subscriptions Backport implementation guide. 042 * This builder provides a fluent API to create these resources without needing to 043 * handle extension management directly. 044 * 045 * @see SubscriptionTopicCanonicalizer For conversion between R4 Basic and R5 SubscriptionTopic 046 */ 047public class R4SubscriptionTopicBuilder { 048 049 private final Basic myTopic; 050 private Extension myCurrentResourceTrigger; 051 private Extension myCurrentCanFilterBy; 052 private Extension myCurrentNotificationShape; 053 054 /** 055 * Creates a new builder with a Basic resource having the SubscriptionTopic code 056 */ 057 public R4SubscriptionTopicBuilder() { 058 myTopic = new Basic(); 059 060 // Set the Basic.code to indicate this is a SubscriptionTopic 061 CodeableConcept code = new CodeableConcept(); 062 code.addCoding(new Coding().setSystem("http://hl7.org/fhir/fhir-types").setCode("SubscriptionTopic")); 063 myTopic.setCode(code); 064 } 065 066 /** 067 * Set the logical ID of the SubscriptionTopic 068 */ 069 public R4SubscriptionTopicBuilder setId(String theId) { 070 myTopic.setId(theId); 071 return this; 072 } 073 074 /** 075 * Set the canonical URL of the topic 076 */ 077 public R4SubscriptionTopicBuilder setUrl(String theUrl) { 078 addExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_URL, new UriType(theUrl)); 079 return this; 080 } 081 082 /** 083 * Set the version of the topic 084 */ 085 public R4SubscriptionTopicBuilder setVersion(String theVersion) { 086 addExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_VERSION, new StringType(theVersion)); 087 return this; 088 } 089 090 /** 091 * Set the name of the topic 092 */ 093 public R4SubscriptionTopicBuilder setName(String theName) { 094 addExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_NAME, new StringType(theName)); 095 return this; 096 } 097 098 /** 099 * Set the title of the topic 100 */ 101 public R4SubscriptionTopicBuilder setTitle(String theTitle) { 102 addExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_TITLE, new StringType(theTitle)); 103 return this; 104 } 105 106 /** 107 * Set the date the topic was last updated 108 */ 109 public R4SubscriptionTopicBuilder setDate(Date theDate) { 110 addExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_DATE, new DateTimeType(theDate)); 111 return this; 112 } 113 114 /** 115 * Set the description of the topic 116 */ 117 public R4SubscriptionTopicBuilder setDescription(String theDescription) { 118 addExtension( 119 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_DESCRIPTION, new MarkdownType(theDescription)); 120 return this; 121 } 122 123 /** 124 * Set the status of the topic (note: uses modifier extension) 125 */ 126 public R4SubscriptionTopicBuilder setStatus(PublicationStatus theStatus) { 127 Extension statusExtension = new Extension() 128 .setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_TOPIC_STATUS) 129 .setValue(new StringType(theStatus.toCode())); 130 myTopic.addModifierExtension(statusExtension); 131 return this; 132 } 133 134 /** 135 * Start defining a resource trigger 136 */ 137 public R4SubscriptionTopicBuilder addResourceTrigger() { 138 myCurrentResourceTrigger = 139 new Extension().setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_RESOURCE_TRIGGER); 140 myTopic.addExtension(myCurrentResourceTrigger); 141 return this; 142 } 143 144 /** 145 * Set the description for the current resource trigger 146 */ 147 public R4SubscriptionTopicBuilder setResourceTriggerDescription(String theDescription) { 148 checkCurrentResourceTrigger(); 149 addNestedExtension( 150 myCurrentResourceTrigger, 151 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_DESCRIPTION, 152 new MarkdownType(theDescription)); 153 return this; 154 } 155 156 /** 157 * Set the resource type for the current resource trigger 158 */ 159 public R4SubscriptionTopicBuilder setResourceTriggerResource(String theResourceType) { 160 checkCurrentResourceTrigger(); 161 addNestedExtension( 162 myCurrentResourceTrigger, 163 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_RESOURCE, 164 new UriType(theResourceType)); 165 return this; 166 } 167 168 /** 169 * Add a supported interaction to the current resource trigger 170 */ 171 public R4SubscriptionTopicBuilder addResourceTriggerSupportedInteraction(String theInteractionCode) { 172 checkCurrentResourceTrigger(); 173 addNestedExtension( 174 myCurrentResourceTrigger, 175 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_SUPPORTED_INTERACTION, 176 new StringType(theInteractionCode)); 177 return this; 178 } 179 180 /** 181 * Add FHIRPath criteria to the current resource trigger 182 */ 183 public R4SubscriptionTopicBuilder setResourceTriggerFhirPathCriteria(String theFhirPathExpression) { 184 checkCurrentResourceTrigger(); 185 addNestedExtension( 186 myCurrentResourceTrigger, 187 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_FHIRPATH_CRITERIA, 188 new StringType(theFhirPathExpression)); 189 return this; 190 } 191 192 /** 193 * Start defining a query criteria extension for the current resource trigger 194 */ 195 public R4SubscriptionTopicBuilder addResourceTriggerQueryCriteria() { 196 checkCurrentResourceTrigger(); 197 Extension queryCriteria = 198 new Extension().setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_QUERY_CRITERIA); 199 myCurrentResourceTrigger.addExtension(queryCriteria); 200 return this; 201 } 202 203 /** 204 * Set the previous query string for the current resource trigger's query criteria 205 */ 206 public R4SubscriptionTopicBuilder setResourceTriggerQueryCriteriaPrevious(String thePreviousQuery) { 207 checkCurrentResourceTrigger(); 208 Extension queryCriteria = getOrCreateQueryCriteriaExtension(); 209 queryCriteria.addExtension(new Extension() 210 .setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_QUERY_CRITERIA_PREVIOUS) 211 .setValue(new StringType(thePreviousQuery))); 212 return this; 213 } 214 215 /** 216 * Set the current query string for the current resource trigger's query criteria 217 */ 218 public R4SubscriptionTopicBuilder setResourceTriggerQueryCriteriaCurrent(String theCurrentQuery) { 219 checkCurrentResourceTrigger(); 220 Extension queryCriteria = getOrCreateQueryCriteriaExtension(); 221 queryCriteria.addExtension(new Extension() 222 .setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_QUERY_CRITERIA_CURRENT) 223 .setValue(new StringType(theCurrentQuery))); 224 return this; 225 } 226 227 /** 228 * Set requireBoth flag for the current resource trigger's query criteria 229 */ 230 public R4SubscriptionTopicBuilder setResourceTriggerQueryCriteriaRequireBoth(boolean theRequireBoth) { 231 checkCurrentResourceTrigger(); 232 Extension queryCriteria = getOrCreateQueryCriteriaExtension(); 233 queryCriteria.addExtension(new Extension() 234 .setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_QUERY_CRITERIA_REQUIRE_BOTH) 235 .setValue(new BooleanType(theRequireBoth))); 236 return this; 237 } 238 239 /** 240 * Start defining a can-filter-by extension 241 */ 242 public R4SubscriptionTopicBuilder addCanFilterBy() { 243 myCurrentCanFilterBy = new Extension().setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_CAN_FILTER_BY); 244 myTopic.addExtension(myCurrentCanFilterBy); 245 return this; 246 } 247 248 /** 249 * Set the description for the current can-filter-by 250 */ 251 public R4SubscriptionTopicBuilder setCanFilterByDescription(String theDescription) { 252 checkCurrentCanFilterBy(); 253 addNestedExtension( 254 myCurrentCanFilterBy, 255 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_DESCRIPTION, 256 new MarkdownType(theDescription)); 257 return this; 258 } 259 260 /** 261 * Set the resource type for the current can-filter-by 262 */ 263 public R4SubscriptionTopicBuilder setCanFilterByResource(String theResourceType) { 264 checkCurrentCanFilterBy(); 265 addNestedExtension( 266 myCurrentCanFilterBy, 267 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_RESOURCE, 268 new UriType(theResourceType)); 269 return this; 270 } 271 272 /** 273 * Set the filter parameter for the current can-filter-by 274 */ 275 public R4SubscriptionTopicBuilder setCanFilterByParameter(String theFilterParameter) { 276 checkCurrentCanFilterBy(); 277 addNestedExtension( 278 myCurrentCanFilterBy, 279 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_FILTER_PARAMETER, 280 new StringType(theFilterParameter)); 281 return this; 282 } 283 284 /** 285 * Start defining a notification shape extension 286 */ 287 public R4SubscriptionTopicBuilder addNotificationShape() { 288 myCurrentNotificationShape = 289 new Extension().setUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_NOTIFICATION_SHAPE); 290 myTopic.addExtension(myCurrentNotificationShape); 291 return this; 292 } 293 294 /** 295 * Set the resource type for the current notification shape 296 */ 297 public R4SubscriptionTopicBuilder setNotificationShapeResource(String theResourceType) { 298 checkCurrentNotificationShape(); 299 addNestedExtension( 300 myCurrentNotificationShape, 301 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_RESOURCE, 302 new UriType(theResourceType)); 303 return this; 304 } 305 306 /** 307 * Add an include parameter to the current notification shape 308 */ 309 public R4SubscriptionTopicBuilder addNotificationShapeInclude(String theIncludeParam) { 310 checkCurrentNotificationShape(); 311 addNestedExtension( 312 myCurrentNotificationShape, 313 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_INCLUDE, 314 new StringType(theIncludeParam)); 315 return this; 316 } 317 318 /** 319 * Add a revInclude parameter to the current notification shape 320 */ 321 public R4SubscriptionTopicBuilder addNotificationShapeRevInclude(String theRevIncludeParam) { 322 checkCurrentNotificationShape(); 323 addNestedExtension( 324 myCurrentNotificationShape, 325 SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_REVINCLUDE, 326 new StringType(theRevIncludeParam)); 327 return this; 328 } 329 330 /** 331 * Return the built Basic resource representing the SubscriptionTopic 332 */ 333 public Basic build() { 334 return myTopic; 335 } 336 337 // Helper methods 338 339 private void checkCurrentResourceTrigger() { 340 if (myCurrentResourceTrigger == null) { 341 throw new IllegalStateException("No current resource trigger defined. Call addResourceTrigger() first."); 342 } 343 } 344 345 private void checkCurrentCanFilterBy() { 346 if (myCurrentCanFilterBy == null) { 347 throw new IllegalStateException("No current can-filter-by defined. Call addCanFilterBy() first."); 348 } 349 } 350 351 private void checkCurrentNotificationShape() { 352 if (myCurrentNotificationShape == null) { 353 throw new IllegalStateException( 354 "No current notification shape defined. Call addNotificationShape() first."); 355 } 356 } 357 358 private Extension getOrCreateQueryCriteriaExtension() { 359 String queryExtensionUrl = SubscriptionConstants.SUBSCRIPTION_TOPIC_R4_EXT_QUERY_CRITERIA; 360 361 // Try to find existing extension 362 return myCurrentResourceTrigger.getExtension().stream() 363 .filter(extension -> queryExtensionUrl.equals(extension.getUrl())) 364 .findFirst() 365 .orElseGet(() -> { 366 // Create and add new extension if none exists 367 Extension queryCriteria = new Extension().setUrl(queryExtensionUrl); 368 myCurrentResourceTrigger.addExtension(queryCriteria); 369 return queryCriteria; 370 }); 371 } 372 373 private void addExtension(String theUrl, org.hl7.fhir.r4.model.Type theValue) { 374 Extension extension = new Extension().setUrl(theUrl).setValue(theValue); 375 myTopic.addExtension(extension); 376 } 377 378 private void addNestedExtension(Extension theParent, String theUrl, org.hl7.fhir.r4.model.Type theValue) { 379 Extension nestedExtension = new Extension().setUrl(theUrl).setValue(theValue); 380 theParent.addExtension(nestedExtension); 381 } 382}