001package ca.uhn.fhir.jpa.subscription.module; 002 003/*- 004 * #%L 005 * HAPI FHIR Subscription Server 006 * %% 007 * Copyright (C) 2014 - 2020 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseResourceMessage; 025import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceMessage; 026import ca.uhn.fhir.util.ResourceReferenceInfo; 027import com.fasterxml.jackson.annotation.JsonAutoDetect; 028import com.fasterxml.jackson.annotation.JsonIgnore; 029import com.fasterxml.jackson.annotation.JsonInclude; 030import com.fasterxml.jackson.annotation.JsonProperty; 031import org.apache.commons.lang3.builder.ToStringBuilder; 032import org.hl7.fhir.instance.model.api.IBaseResource; 033import org.hl7.fhir.instance.model.api.IIdType; 034 035import java.util.List; 036 037import static org.apache.commons.lang3.StringUtils.isBlank; 038import static org.apache.commons.lang3.StringUtils.isNotBlank; 039 040@JsonInclude(JsonInclude.Include.NON_NULL) 041@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) 042public class ResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage { 043 044 @JsonProperty("resourceId") 045 private String myId; 046 @JsonProperty("operationType") 047 private OperationTypeEnum myOperationType; 048 /** 049 * This will only be set if the resource is being triggered for a specific 050 * subscription 051 */ 052 @JsonProperty(value = "subscriptionId", required = false) 053 private String mySubscriptionId; 054 @JsonProperty("payload") 055 private String myPayload; 056 @JsonProperty("payloadId") 057 private String myPayloadId; 058 @JsonIgnore 059 private transient IBaseResource myPayloadDecoded; 060 061 /** 062 * Constructor 063 */ 064 public ResourceModifiedMessage() { 065 super(); 066 } 067 068 public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) { 069 this(); 070 setId(theResource.getIdElement()); 071 setOperationType(theOperationType); 072 if (theOperationType != OperationTypeEnum.DELETE) { 073 setNewPayload(theFhirContext, theResource); 074 } 075 } 076 077 @Override 078 public String getPayloadId() { 079 return myPayloadId; 080 } 081 082 public String getSubscriptionId() { 083 return mySubscriptionId; 084 } 085 086 public void setSubscriptionId(String theSubscriptionId) { 087 mySubscriptionId = theSubscriptionId; 088 } 089 090 public String getId() { 091 return myId; 092 } 093 094 public IIdType getId(FhirContext theCtx) { 095 IIdType retVal = null; 096 if (myId != null) { 097 retVal = theCtx.getVersion().newIdType().setValue(myId); 098 } 099 return retVal; 100 } 101 102 public IBaseResource getNewPayload(FhirContext theCtx) { 103 if (myPayloadDecoded == null && isNotBlank(myPayload)) { 104 myPayloadDecoded = theCtx.newJsonParser().parseResource(myPayload); 105 } 106 return myPayloadDecoded; 107 } 108 109 public OperationTypeEnum getOperationType() { 110 return myOperationType; 111 } 112 113 public void setOperationType(OperationTypeEnum theOperationType) { 114 myOperationType = theOperationType; 115 } 116 117 public void setId(IIdType theId) { 118 myId = null; 119 if (theId != null) { 120 myId = theId.getValue(); 121 } 122 } 123 124 private void setNewPayload(FhirContext theCtx, IBaseResource theNewPayload) { 125 /* 126 * References with placeholders would be invalid by the time we get here, and 127 * would be caught before we even get here. This check is basically a last-ditch 128 * effort to make sure nothing has broken in the various safeguards that 129 * should prevent this from happening (hence it only being an assert as 130 * opposed to something executed all the time). 131 */ 132 assert payloadContainsNoPlaceholderReferences(theCtx, theNewPayload); 133 134 /* 135 * Note: Don't set myPayloadDecoded in here- This is a false optimization since 136 * it doesn't actually get used if anyone is doing subscriptions at any 137 * scale using a queue engine, and not going through the serialize/deserialize 138 * as we would in a queue engine can mask bugs. 139 * -JA 140 */ 141 myPayload = theCtx.newJsonParser().encodeResourceToString(theNewPayload); 142 myPayloadId = theNewPayload.getIdElement().toUnqualified().getValue(); 143 } 144 145 public enum OperationTypeEnum { 146 CREATE, 147 UPDATE, 148 DELETE, 149 MANUALLY_TRIGGERED 150 151 } 152 153 private static boolean payloadContainsNoPlaceholderReferences(FhirContext theCtx, IBaseResource theNewPayload) { 154 List<ResourceReferenceInfo> refs = theCtx.newTerser().getAllResourceReferences(theNewPayload); 155 for (ResourceReferenceInfo next : refs) { 156 String ref = next.getResourceReference().getReferenceElement().getValue(); 157 if (isBlank(ref)) { 158 IBaseResource resource = next.getResourceReference().getResource(); 159 if (resource != null) { 160 ref = resource.getIdElement().getValue(); 161 } 162 } 163 if (isNotBlank(ref)) { 164 if (ref.startsWith("#")) { 165 continue; 166 } 167 if (ref.startsWith("urn:uuid:")) { 168 throw new AssertionError("Reference at " + next.getName() + " is invalid: " + ref); 169 } 170 } 171 } 172 return true; 173 } 174 175 @Override 176 public String toString() { 177 return new ToStringBuilder(this) 178 .append("myId", myId) 179 .append("myOperationType", myOperationType) 180 .append("mySubscriptionId", mySubscriptionId) 181// .append("myPayload", myPayload) 182 .append("myPayloadId", myPayloadId) 183// .append("myPayloadDecoded", myPayloadDecoded) 184 .toString(); 185 } 186}