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